Refactor zwave discovery to entity schema (#6445)

* Refactor zwave discovery to entity schema

* Address PR concerns

* Split DISCOVERY_SCHEMAS into separate file

* Only check cover reverse workaround once
This commit is contained in:
Adam Mills 2017-03-13 02:13:34 -04:00 committed by Paulus Schoutsen
parent 55d60a6a13
commit 56abc7f9b4
11 changed files with 658 additions and 565 deletions

View File

@ -19,34 +19,34 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = [] DEPENDENCIES = []
def get_device(value, **kwargs): def get_device(values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
device_mapping = workaround.get_device_mapping(value) device_mapping = workaround.get_device_mapping(values.primary)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT: if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4 # Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4) re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4
return ZWaveTriggerSensor(value, "motion", re_arm_multiplier * 8) return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8)
if workaround.get_device_component_mapping(value) == DOMAIN: if workaround.get_device_component_mapping(values.primary) == DOMAIN:
return ZWaveBinarySensor(value, None) return ZWaveBinarySensor(values, None)
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY: if values.primary.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
return ZWaveBinarySensor(value, None) return ZWaveBinarySensor(values, None)
return None return None
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
"""Representation of a binary sensor within Z-Wave.""" """Representation of a binary sensor within Z-Wave."""
def __init__(self, value, device_class): def __init__(self, values, device_class):
"""Initialize the sensor.""" """Initialize the sensor."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._sensor_type = device_class self._sensor_type = device_class
self._state = self._value.data self._state = self.values.primary.data
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
self._state = self._value.data self._state = self.values.primary.data
@property @property
def is_on(self): def is_on(self):
@ -62,15 +62,15 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor): class ZWaveTriggerSensor(ZWaveBinarySensor):
"""Representation of a stateless sensor within Z-Wave.""" """Representation of a stateless sensor within Z-Wave."""
def __init__(self, value, device_class, re_arm_sec=60): def __init__(self, values, device_class, re_arm_sec=60):
"""Initialize the sensor.""" """Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(value, device_class) super(ZWaveTriggerSensor, self).__init__(values, device_class)
self.re_arm_sec = re_arm_sec self.re_arm_sec = re_arm_sec
self.invalidate_after = None self.invalidate_after = None
def update_properties(self): def update_properties(self):
"""Called when a value for this entity's node has changed.""" """Called when a value for this entity's node has changed."""
self._state = self._value.data self._state = self.values.primary.data
# only allow this value to be true for re_arm secs # only allow this value to be true for re_arm secs
if not self.hass: if not self.hass:
return return

View File

@ -10,7 +10,6 @@ import logging
from homeassistant.components.climate import DOMAIN from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import ZWaveDeviceEntity from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import ( from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@ -33,20 +32,18 @@ DEVICE_MAPPINGS = {
} }
def get_device(hass, value, **kwargs): def get_device(hass, values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
temp_unit = hass.config.units.temperature_unit temp_unit = hass.config.units.temperature_unit
return ZWaveClimate(value, temp_unit) return ZWaveClimate(values, temp_unit)
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Representation of a Z-Wave Climate device.""" """Representation of a Z-Wave Climate device."""
def __init__(self, value, temp_unit): def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device.""" """Initialize the Z-Wave climate device."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN) ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._index = value.index
self._node = value.node
self._target_temperature = None self._target_temperature = None
self._current_temperature = None self._current_temperature = None
self._current_operation = None self._current_operation = None
@ -61,10 +58,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("temp_unit is %s", self._unit) _LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None self._zxt_120 = None
# Make sure that we have values for the key before converting to int # Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and if (self.node.manufacturer_id.strip() and
value.node.product_id.strip()): self.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16), specific_sensor_key = (
int(value.node.product_id, 16)) int(self.node.manufacturer_id, 16),
int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS: if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120:
_LOGGER.debug("Remotec ZXT-120 Zwave Thermostat" _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat"
@ -75,81 +73,58 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
# Operation Mode # Operation Mode
self._current_operation = self.get_value( if self.values.mode:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data') self._current_operation = self.values.mode.data
operation_list = self.get_value( operation_list = self.values.mode.data_items
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, if operation_list:
member='data_items') self._operation_list = list(operation_list)
if operation_list:
self._operation_list = list(operation_list)
_LOGGER.debug("self._operation_list=%s", self._operation_list) _LOGGER.debug("self._operation_list=%s", self._operation_list)
_LOGGER.debug("self._current_operation=%s", self._current_operation) _LOGGER.debug("self._current_operation=%s", self._current_operation)
# Current Temp # Current Temp
self._current_temperature = self.get_value( if self.values.temperature:
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, self._current_temperature = self.values.temperature.data
label=['Temperature'], member='data') device_unit = self.values.temperature.units
device_unit = self.get_value( if device_unit is not None:
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, self._unit = device_unit
label=['Temperature'], member='units')
if device_unit is not None:
self._unit = device_unit
# Fan Mode # Fan Mode
self._current_fan_mode = self.get_value( if self.values.fan_mode:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, self._current_fan_mode = self.values.fan_mode.data
member='data') fan_list = self.values.fan_mode.data_items
fan_list = self.get_value( if fan_list:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, self._fan_list = list(fan_list)
member='data_items')
if fan_list:
self._fan_list = list(fan_list)
_LOGGER.debug("self._fan_list=%s", self._fan_list) _LOGGER.debug("self._fan_list=%s", self._fan_list)
_LOGGER.debug("self._current_fan_mode=%s", _LOGGER.debug("self._current_fan_mode=%s",
self._current_fan_mode) self._current_fan_mode)
# Swing mode # Swing mode
if self._zxt_120 == 1: if self._zxt_120 == 1:
self._current_swing_mode = ( if self.values.zxt_120_swing_mode:
self.get_value( self._current_swing_mode = self.values.zxt_120_swing_mode.data
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, swing_list = self.values.zxt_120_swing_mode.data_items
index=33, if swing_list:
member='data')) self._swing_list = list(swing_list)
swing_list = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=33,
member='data_items')
if swing_list:
self._swing_list = list(swing_list)
_LOGGER.debug("self._swing_list=%s", self._swing_list) _LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s", _LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode) self._current_swing_mode)
# Set point # Set point
temps = [] if self.values.primary.data == 0:
for value in ( _LOGGER.debug("Setpoint is 0, setting default to "
self._node.get_values( "current_temperature=%s",
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT) self._current_temperature)
.values()): self._target_temperature = (
temps.append((round(float(value.data)), 1)) round((float(self._current_temperature)), 1))
if value.index == self._index: else:
if value.data == 0: self._target_temperature = round(
_LOGGER.debug("Setpoint is 0, setting default to " (float(self.values.primary.data)), 1)
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
break
else:
self._target_temperature = round((float(value.data)), 1)
# Operating state # Operating state
self._operating_state = self.get_value( if self.values.operating_state:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, self._operating_state = self.values.operating_state.data
member='data')
# Fan operating state # Fan operating state
self._fan_state = self.get_value( if self.values.fan_state:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE, self._fan_state = self.values.fan_state.data
member='data')
@property @property
def should_poll(self): def should_poll(self):
@ -213,29 +188,24 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else: else:
return return
self.set_value( self.values.primary.data = temperature
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_fan_mode(self, fan): def set_fan_mode(self, fan):
"""Set new target fan mode.""" """Set new target fan mode."""
self.set_value( if self.values.fan_mode:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, self.values.fan_mode.data = bytes(fan, 'utf-8')
index=0, data=bytes(fan, 'utf-8'))
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set new target operation mode.""" """Set new target operation mode."""
self.set_value( if self.values.mode:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, self.values.mode.data = bytes(operation_mode, 'utf-8')
index=0, data=bytes(operation_mode, 'utf-8'))
def set_swing_mode(self, swing_mode): def set_swing_mode(self, swing_mode):
"""Set new target swing mode.""" """Set new target swing mode."""
if self._zxt_120 == 1: if self._zxt_120 == 1:
self.set_value( if self.values.zxt_120_swing_mode:
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, self.values.zxt_120_swing_mode = bytes(swing_mode, 'utf-8')
index=33, data=bytes(swing_mode, 'utf-8'))
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -246,8 +216,3 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
if self._fan_state: if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state data[ATTR_FAN_STATE] = self._fan_state
return data return data
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
return None

View File

@ -20,64 +20,49 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
def get_device(value, **kwargs): def get_device(values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL if (values.primary.command_class ==
and value.index == 0): zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
return ZwaveRollershutter(value) and values.primary.index == 0):
elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or return ZwaveRollershutter(values)
value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR): elif (values.primary.command_class in [
return ZwaveGarageDoor(value) zwave.const.COMMAND_CLASS_SWITCH_BINARY,
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]):
return ZwaveGarageDoor(values)
return None return None
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave roller shutter.""" """Representation of an Zwave roller shutter."""
def __init__(self, value): def __init__(self, values):
"""Initialize the zwave rollershutter.""" """Initialize the zwave rollershutter."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN) ZWaveDeviceEntity.__init__(self, values, DOMAIN)
# pylint: disable=no-member # pylint: disable=no-member
self._node = value.node
self._open_id = None self._open_id = None
self._close_id = None self._close_id = None
self._current_position_id = None
self._current_position = None self._current_position = None
self._workaround = workaround.get_device_mapping(value) self._workaround = workaround.get_device_mapping(values.primary)
if self._workaround: if self._workaround:
_LOGGER.debug("Using workaround %s", self._workaround) _LOGGER.debug("Using workaround %s", self._workaround)
self.update_properties() self.update_properties()
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
if not self._node.is_ready:
return None
return [self._current_position_id]
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
# Position value # Position value
if not self._node.is_ready: self._current_position = self.values.primary.data
if self._current_position_id is None:
self._current_position_id = self.get_value( if self.values.open and self.values.close and \
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, self._open_id is None and self._close_id is None:
label=['Level'], member='value_id') if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
if self._open_id is None: self._open_id = self.values.close.value_id
self._open_id = self.get_value( self._close_id = self.values.open.value_id
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, self._workaround = None
label=['Open', 'Up'], member='value_id') else:
if self._close_id is None: self._open_id = self.values.open.value_id
self._close_id = self.get_value( self._close_id = self.values.close.value_id
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
if self._open_id and self._close_id and \
self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
self._open_id, self._close_id = self._close_id, self._open_id
self._workaround = None
self._current_position = self._node.get_dimmer_level(
self._current_position_id)
@property @property
def is_closed(self): def is_closed(self):
@ -112,7 +97,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def set_cover_position(self, position, **kwargs): def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position.""" """Move the roller shutter to a specific position."""
self._node.set_dimmer(self._value.value_id, position) self.node.set_dimmer(self.values.primary.value_id, position)
def stop_cover(self, **kwargs): def stop_cover(self, **kwargs):
"""Stop the roller shutter.""" """Stop the roller shutter."""
@ -122,14 +107,14 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave garage door device.""" """Representation of an Zwave garage door device."""
def __init__(self, value): def __init__(self, values):
"""Initialize the zwave garage door.""" """Initialize the zwave garage door."""
ZWaveDeviceEntity.__init__(self, value, DOMAIN) ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.update_properties() self.update_properties()
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
self._state = self._value.data self._state = self.values.primary.data
@property @property
def is_closed(self): def is_closed(self):
@ -138,11 +123,11 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice):
def close_cover(self): def close_cover(self):
"""Close the garage door.""" """Close the garage door."""
self._value.data = False self.values.primary.data = False
def open_cover(self): def open_cover(self):
"""Open the garage door.""" """Open the garage door."""
self._value.data = True self.values.primary.data = True
@property @property
def device_class(self): def device_class(self):

View File

@ -49,9 +49,9 @@ SUPPORT_ZWAVE_COLORTEMP = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR
| SUPPORT_COLOR_TEMP) | SUPPORT_COLOR_TEMP)
def get_device(node, value, node_config, **kwargs): def get_device(node, values, node_config, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
name = '{}.{}'.format(DOMAIN, zwave.object_id(value)) name = '{}.{}'.format(DOMAIN, zwave.object_id(values.primary))
refresh = node_config.get(zwave.CONF_REFRESH_VALUE) refresh = node_config.get(zwave.CONF_REFRESH_VALUE)
delay = node_config.get(zwave.CONF_REFRESH_DELAY) delay = node_config.get(zwave.CONF_REFRESH_DELAY)
_LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s' _LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s'
@ -59,9 +59,9 @@ def get_device(node, value, node_config, **kwargs):
refresh, delay) refresh, delay)
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR): if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR):
return ZwaveColorLight(value, refresh, delay) return ZwaveColorLight(values, refresh, delay)
else: else:
return ZwaveDimmer(value, refresh, delay) return ZwaveDimmer(values, refresh, delay)
def brightness_state(value): def brightness_state(value):
@ -75,9 +75,9 @@ def brightness_state(value):
class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
"""Representation of a Z-Wave dimmer.""" """Representation of a Z-Wave dimmer."""
def __init__(self, value, refresh, delay): def __init__(self, values, refresh, delay):
"""Initialize the light.""" """Initialize the light."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._brightness = None self._brightness = None
self._state = None self._state = None
self._delay = delay self._delay = delay
@ -86,10 +86,10 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
# Enable appropriate workaround flags for our device # Enable appropriate workaround flags for our device
# Make sure that we have values for the key before converting to int # Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and if (self.node.manufacturer_id.strip() and
value.node.product_id.strip()): self.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16), specific_sensor_key = (int(self.node.manufacturer_id, 16),
int(value.node.product_id, 16)) int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS: if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098:
_LOGGER.debug("AEOTEC ZW098 workaround enabled") _LOGGER.debug("AEOTEC ZW098 workaround enabled")
@ -105,7 +105,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
def update_properties(self): def update_properties(self):
"""Update internal properties based on zwave values.""" """Update internal properties based on zwave values."""
# Brightness # Brightness
self._brightness, self._state = brightness_state(self._value) self._brightness, self._state = brightness_state(self.values.primary)
def value_changed(self): def value_changed(self):
"""Called when a value for this entity's node has changed.""" """Called when a value for this entity's node has changed."""
@ -116,7 +116,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
def _refresh_value(): def _refresh_value():
"""Used timer callback for delayed value refresh.""" """Used timer callback for delayed value refresh."""
self._refreshing = True self._refreshing = True
self._value.refresh() self.values.primary.refresh()
if self._timer is not None and self._timer.isAlive(): if self._timer is not None and self._timer.isAlive():
self._timer.cancel() self._timer.cancel()
@ -151,12 +151,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
else: else:
brightness = 255 brightness = 255
if self._value.node.set_dimmer(self._value.value_id, brightness): if self.node.set_dimmer(self.values.primary.value_id, brightness):
self._state = STATE_ON self._state = STATE_ON
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the device off.""" """Turn the device off."""
if self._value.node.set_dimmer(self._value.value_id, 0): if self.node.set_dimmer(self.values.primary.value_id, 0):
self._state = STATE_OFF self._state = STATE_OFF
@ -170,73 +170,28 @@ def ct_to_rgb(temp):
class ZwaveColorLight(ZwaveDimmer): class ZwaveColorLight(ZwaveDimmer):
"""Representation of a Z-Wave color changing light.""" """Representation of a Z-Wave color changing light."""
def __init__(self, value, refresh, delay): def __init__(self, values, refresh, delay):
"""Initialize the light.""" """Initialize the light."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
self._value_color = None
self._value_color_channels = None
self._color_channels = None self._color_channels = None
self._rgb = None self._rgb = None
self._ct = None self._ct = None
super().__init__(value, refresh, delay) super().__init__(values, refresh, delay)
# Create a listener so the color values can be linked to this entity
dispatcher.connect(
self._value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED)
self._get_color_values()
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
return [val.value_id for val in [
self._value_color, self._value_color_channels] if val]
def _get_color_values(self):
"""Search for color values available on this node."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
_LOGGER.debug("Searching for zwave color values")
# Currently zwave nodes only exist with one color element per node.
if self._value_color is None:
for value_color in self._value.node.get_rgbbulbs().values():
self._value_color = value_color
if self._value_color_channels is None:
self._value_color_channels = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_COLOR,
genre=zwave.const.GENRE_SYSTEM, type=zwave.const.TYPE_INT)
if self._value_color and self._value_color_channels:
_LOGGER.debug("Zwave node color values found.")
dispatcher.disconnect(
self._value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED)
self.update_properties()
def _value_added(self, value):
"""Called when a value has been added to the network."""
if self._value.node != value.node:
return
# Check for the missing color values
self._get_color_values()
def update_properties(self): def update_properties(self):
"""Update internal properties based on zwave values.""" """Update internal properties based on zwave values."""
super().update_properties() super().update_properties()
if self._value_color is None: if self.values.color is None:
return return
if self._value_color_channels is None: if self.values.color_channels is None:
return return
# Color Channels # Color Channels
self._color_channels = self._value_color_channels.data self._color_channels = self.values.color_channels.data
# Color Data String # Color Data String
data = self._value_color.data data = self.values.color.data
# RGB is always present in the openzwave color data string. # RGB is always present in the openzwave color data string.
self._rgb = [ self._rgb = [
@ -329,8 +284,8 @@ class ZwaveColorLight(ZwaveDimmer):
rgbw += format(colorval, '02x').encode('utf-8') rgbw += format(colorval, '02x').encode('utf-8')
rgbw += b'0000' rgbw += b'0000'
if rgbw and self._value_color: if rgbw and self.values.color:
self._value_color.node.set_rgbw(self._value_color.value_id, rgbw) self.values.color.data = rgbw
super().turn_on(**kwargs) super().turn_on(**kwargs)

View File

@ -120,7 +120,7 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({
}) })
def get_device(hass, node, value, **kwargs): def get_device(hass, node, values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml')) path.join(path.dirname(__file__), 'services.yaml'))
@ -191,16 +191,15 @@ def get_device(hass, node, value, **kwargs):
clear_usercode, clear_usercode,
descriptions.get(SERVICE_CLEAR_USERCODE), descriptions.get(SERVICE_CLEAR_USERCODE),
schema=CLEAR_USERCODE_SCHEMA) schema=CLEAR_USERCODE_SCHEMA)
return ZwaveLock(value) return ZwaveLock(values)
class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
"""Representation of a Z-Wave Lock.""" """Representation of a Z-Wave Lock."""
def __init__(self, value): def __init__(self, values):
"""Initialize the Z-Wave lock device.""" """Initialize the Z-Wave lock device."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._node = value.node
self._state = None self._state = None
self._notification = None self._notification = None
self._lock_status = None self._lock_status = None
@ -208,10 +207,10 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
# Enable appropriate workaround flags for our device # Enable appropriate workaround flags for our device
# Make sure that we have values for the key before converting to int # Make sure that we have values for the key before converting to int
if (value.node.manufacturer_id.strip() and if (self.node.manufacturer_id.strip() and
value.node.product_id.strip()): self.node.product_id.strip()):
specific_sensor_key = (int(value.node.manufacturer_id, 16), specific_sensor_key = (int(self.node.manufacturer_id, 16),
int(value.node.product_id, 16)) int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS: if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_V2BTZE: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_V2BTZE:
self._v2btze = 1 self._v2btze = 1
@ -221,36 +220,33 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
self._state = self._value.data self._state = self.values.primary.data
_LOGGER.debug('Lock state set from Bool value and' _LOGGER.debug('Lock state set from Bool value and'
' is %s', self._state) ' is %s', self._state)
notification_data = self.get_value(class_id=zwave.const if self.values.access_control:
.COMMAND_CLASS_ALARM, notification_data = self.values.access_control.data
label=['Access Control'],
member='data')
if notification_data:
self._notification = LOCK_NOTIFICATION.get(str(notification_data)) self._notification = LOCK_NOTIFICATION.get(str(notification_data))
if self._v2btze:
advanced_config = self.get_value(class_id=zwave.const
.COMMAND_CLASS_CONFIGURATION,
index=12,
data=CONFIG_ADVANCED,
member='data')
if advanced_config:
self._state = LOCK_STATUS.get(str(notification_data))
_LOGGER.debug('Lock state set from Access Control '
'value and is %s, get=%s',
str(notification_data),
self.state)
alarm_type = self.get_value(class_id=zwave.const if self._v2btze:
.COMMAND_CLASS_ALARM, if self.values.v2btze_advanced and \
label=['Alarm Type'], member='data') self.values.v2btze_advanced.data == CONFIG_ADVANCED:
self._state = LOCK_STATUS.get(str(notification_data))
_LOGGER.debug('Lock state set from Access Control '
'value and is %s, get=%s',
str(notification_data),
self.state)
if not self.values.alarm_type:
return
alarm_type = self.values.alarm_type.data
_LOGGER.debug('Lock alarm_type is %s', str(alarm_type)) _LOGGER.debug('Lock alarm_type is %s', str(alarm_type))
alarm_level = self.get_value(class_id=zwave.const if self.values.alarm_level:
.COMMAND_CLASS_ALARM, alarm_level = self.values.alarm_level.data
label=['Alarm Level'], member='data') else:
alarm_level = None
_LOGGER.debug('Lock alarm_level is %s', str(alarm_level)) _LOGGER.debug('Lock alarm_level is %s', str(alarm_level))
if not alarm_type: if not alarm_type:
return return
if alarm_type is 21: if alarm_type is 21:
@ -277,11 +273,11 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
def lock(self, **kwargs): def lock(self, **kwargs):
"""Lock the device.""" """Lock the device."""
self._value.data = True self.values.primary.data = True
def unlock(self, **kwargs): def unlock(self, **kwargs):
"""Unlock the device.""" """Unlock the device."""
self._value.data = False self.values.primary.data = False
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -292,8 +288,3 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice):
if self._lock_status: if self._lock_status:
data[ATTR_LOCK_STATUS] = self._lock_status data[ATTR_LOCK_STATUS] = self._lock_status
return data return data
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
return None

View File

@ -15,34 +15,34 @@ from homeassistant.components.zwave import async_setup_platform # noqa # pylint
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_device(node, value, **kwargs): def get_device(node, values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
# Generic Device mappings # Generic Device mappings
if value.command_class == zwave.const.COMMAND_CLASS_BATTERY: if values.primary.command_class == zwave.const.COMMAND_CLASS_BATTERY:
return ZWaveSensor(value) return ZWaveSensor(values)
if node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL): if node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL):
return ZWaveMultilevelSensor(value) return ZWaveMultilevelSensor(values)
if node.has_command_class(zwave.const.COMMAND_CLASS_METER) and \ if node.has_command_class(zwave.const.COMMAND_CLASS_METER) and \
value.type == zwave.const.TYPE_DECIMAL: values.primary.type == zwave.const.TYPE_DECIMAL:
return ZWaveMultilevelSensor(value) return ZWaveMultilevelSensor(values)
if node.has_command_class(zwave.const.COMMAND_CLASS_ALARM) or \ if node.has_command_class(zwave.const.COMMAND_CLASS_ALARM) or \
node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_ALARM): node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_ALARM):
return ZWaveAlarmSensor(value) return ZWaveAlarmSensor(values)
return None return None
class ZWaveSensor(zwave.ZWaveDeviceEntity): class ZWaveSensor(zwave.ZWaveDeviceEntity):
"""Representation of a Z-Wave sensor.""" """Representation of a Z-Wave sensor."""
def __init__(self, value): def __init__(self, values):
"""Initialize the sensor.""" """Initialize the sensor."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.update_properties() self.update_properties()
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
self._state = self._value.data self._state = self.values.primary.data
self._units = self._value.units self._units = self.values.primary.units
@property @property
def force_update(self): def force_update(self):

View File

@ -15,29 +15,30 @@ from homeassistant.components.zwave import workaround, async_setup_platform # n
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_device(value, **kwargs): def get_device(values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
return ZwaveSwitch(value) return ZwaveSwitch(values)
class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice): class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice):
"""Representation of a Z-Wave switch.""" """Representation of a Z-Wave switch."""
def __init__(self, value): def __init__(self, values):
"""Initialize the Z-Wave switch device.""" """Initialize the Z-Wave switch device."""
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.refresh_on_update = (workaround.get_device_mapping(value) == self.refresh_on_update = (
workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE) workaround.get_device_mapping(values.primary) ==
workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE)
self.last_update = time.perf_counter() self.last_update = time.perf_counter()
self._state = self._value.data self._state = self.values.primary.data
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
self._state = self._value.data self._state = self.values.primary.data
if self.refresh_on_update and \ if self.refresh_on_update and \
time.perf_counter() - self.last_update > 30: time.perf_counter() - self.last_update > 30:
self.last_update = time.perf_counter() self.last_update = time.perf_counter()
self._value.node.request_state() self.node.request_state()
@property @property
def is_on(self): def is_on(self):
@ -46,8 +47,8 @@ class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
self._value.node.set_switch(self._value.value_id, True) self.node.set_switch(self.values.primary.value_id, True)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the device off.""" """Turn the device off."""
self._value.node.set_switch(self._value.value_id, False) self.node.set_switch(self.values.primary.value_id, False)

View File

@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zwave/ https://home-assistant.io/components/zwave/
""" """
import asyncio import asyncio
import copy
import logging import logging
import os.path import os.path
import time import time
@ -29,7 +30,8 @@ from homeassistant.helpers.dispatcher import (
from . import const from . import const
from . import workaround from . import workaround
from .util import value_handler from .discovery_schemas import DISCOVERY_SCHEMAS
from .util import check_node_schema, check_value_schema
REQUIREMENTS = ['pydispatcher==2.0.5'] REQUIREMENTS = ['pydispatcher==2.0.5']
@ -64,88 +66,6 @@ DATA_ZWAVE_DICT = 'zwave_devices'
NETWORK = None NETWORK = None
# List of tuple (DOMAIN, discovered service, supported command classes,
# value type, genre type, specific device class).
DISCOVERY_COMPONENTS = [
('sensor',
[const.GENERIC_TYPE_WHATEVER],
[const.SPECIFIC_TYPE_WHATEVER],
[const.COMMAND_CLASS_SENSOR_MULTILEVEL,
const.COMMAND_CLASS_METER,
const.COMMAND_CLASS_ALARM,
const.COMMAND_CLASS_SENSOR_ALARM,
const.COMMAND_CLASS_BATTERY],
const.TYPE_WHATEVER,
const.GENRE_USER),
('light',
[const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_REMOTE],
[const.SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL,
const.SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL,
const.SPECIFIC_TYPE_NOT_USED],
[const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.TYPE_BYTE,
const.GENRE_USER),
('switch',
[const.GENERIC_TYPE_SENSOR_ALARM,
const.GENERIC_TYPE_SENSOR_BINARY,
const.GENERIC_TYPE_SWITCH_BINARY,
const.GENERIC_TYPE_ENTRY_CONTROL,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
const.GENERIC_TYPE_GENERIC_CONTROLLER,
const.GENERIC_TYPE_SWITCH_REMOTE,
const.GENERIC_TYPE_REPEATER_SLAVE,
const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_WALL_CONTROLLER],
[const.SPECIFIC_TYPE_WHATEVER],
[const.COMMAND_CLASS_SWITCH_BINARY],
const.TYPE_BOOL,
const.GENRE_USER),
('binary_sensor',
[const.GENERIC_TYPE_SENSOR_ALARM,
const.GENERIC_TYPE_SENSOR_BINARY,
const.GENERIC_TYPE_SWITCH_BINARY,
const.GENERIC_TYPE_METER,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
const.GENERIC_TYPE_THERMOSTAT],
[const.SPECIFIC_TYPE_WHATEVER],
[const.COMMAND_CLASS_SENSOR_BINARY],
const.TYPE_BOOL,
const.GENRE_USER),
('lock',
[const.GENERIC_TYPE_ENTRY_CONTROL],
[const.SPECIFIC_TYPE_ADVANCED_DOOR_LOCK,
const.SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK],
[const.COMMAND_CLASS_DOOR_LOCK],
const.TYPE_BOOL,
const.GENRE_USER),
('cover',
[const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_ENTRY_CONTROL],
[const.SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL,
const.SPECIFIC_TYPE_MOTOR_MULTIPOSITION,
const.SPECIFIC_TYPE_SECURE_BARRIER_ADDON,
const.SPECIFIC_TYPE_SECURE_DOOR],
[const.COMMAND_CLASS_SWITCH_BINARY,
const.COMMAND_CLASS_BARRIER_OPERATOR,
const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.TYPE_WHATEVER,
const.GENRE_USER),
('climate',
[const.GENERIC_TYPE_THERMOSTAT],
[const.SPECIFIC_TYPE_WHATEVER],
[const.COMMAND_CLASS_THERMOSTAT_SETPOINT],
const.TYPE_WHATEVER,
const.GENRE_WHATEVER),
]
RENAME_NODE_SCHEMA = vol.Schema({ RENAME_NODE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id, vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(const.ATTR_NAME): cv.string, vol.Required(const.ATTR_NAME): cv.string,
@ -358,92 +278,25 @@ def setup(hass, config):
dispatcher.connect(log_all, weak=False) dispatcher.connect(log_all, weak=False)
discovered_values = []
def value_added(node, value): def value_added(node, value):
"""Called when a value is added to a node on the network.""" """Called when a value is added to a node on the network."""
for (component, # Check if this value should be tracked by an existing entity
generic_device_class, for values in discovered_values:
specific_device_class, values.check_value(value)
command_class,
value_type,
value_genre) in DISCOVERY_COMPONENTS:
_LOGGER.debug("Component=%s Node_id=%s query start", for schema in DISCOVERY_SCHEMAS:
component, node.node_id) if not check_node_schema(node, schema):
if node.generic not in generic_device_class and \
None not in generic_device_class:
_LOGGER.debug("node.generic %s not None and in "
"generic_device_class %s",
node.generic, generic_device_class)
continue continue
if node.specific not in specific_device_class and \ if not check_value_schema(
None not in specific_device_class: value,
_LOGGER.debug("node.specific %s is not None and in " schema[const.DISC_INSTANCE_VALUES][const.DISC_PRIMARY]):
"specific_device_class %s", node.specific,
specific_device_class)
continue
if value.command_class not in command_class and \
None not in command_class:
_LOGGER.debug("value.command_class %s is not None "
"and in command_class %s",
value.command_class, command_class)
continue
if value_type != value.type and value_type is not None:
_LOGGER.debug("value.type %s != value_type %s",
value.type, value_type)
continue
if value_genre != value.genre and value_genre is not None:
_LOGGER.debug("value.genre %s != value_genre %s",
value.genre, value_genre)
continue continue
# Configure node values = ZWaveDeviceEntityValues(
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, " hass, schema, value, config, device_config)
"Specific_command_class=%s, " discovered_values.append(values)
"Command_class=%s, Value type=%s, "
"Genre=%s as %s", node.node_id,
node.generic, node.specific,
value.command_class, value.type,
value.genre, component)
workaround_component = workaround.get_device_component_mapping(
value)
if workaround_component and workaround_component != component:
if workaround_component == workaround.WORKAROUND_IGNORE:
_LOGGER.info("Ignoring device %s due to workaround.",
"{}.{}".format(component, object_id(value)))
continue
_LOGGER.debug("Using %s instead of %s",
workaround_component, component)
component = workaround_component
name = "{}.{}".format(component, object_id(value))
node_config = device_config.get(name)
if node_config.get(CONF_IGNORED):
_LOGGER.info(
"Ignoring device %s due to device settings.", name)
return
polling_intensity = convert(
node_config.get(CONF_POLLING_INTENSITY), int)
if polling_intensity:
value.enable_poll(polling_intensity)
else:
value.disable_poll()
platform = get_platform(component, DOMAIN)
device = platform.get_device(
node=node, value=value, node_config=node_config, hass=hass)
if not device:
continue
dict_id = value.value_id
@asyncio.coroutine
def discover_device(component, device, dict_id):
"""Put device in a dictionary and call discovery on it."""
hass.data[DATA_ZWAVE_DICT][dict_id] = device
yield from discovery.async_load_platform(
hass, component, DOMAIN,
{const.DISCOVERY_DEVICE: dict_id}, config)
hass.add_job(discover_device, component, device, dict_id)
def scene_activated(node, scene_id): def scene_activated(node, scene_id):
"""Called when a scene is activated on any node in the network.""" """Called when a scene is activated on any node in the network."""
@ -752,24 +605,166 @@ def setup(hass, config):
return True return True
class ZWaveDeviceEntityValues():
"""Manages entity access to the underlying zwave value objects."""
def __init__(self, hass, schema, primary_value, zwave_config,
device_config):
"""Initialize the values object with the passed entity schema."""
self._hass = hass
self._zwave_config = zwave_config
self._device_config = device_config
self._schema = copy.deepcopy(schema)
self._values = {}
self._entity = None
self._workaround_ignore = False
# Combine node value schemas and instance value schemas into one
# values schema mapping.
self._schema[const.DISC_VALUES] = {}
for name in self._schema[const.DISC_NODE_VALUES].keys():
self._values[name] = None
self._schema[const.DISC_VALUES][name] = \
self._schema[const.DISC_NODE_VALUES][name]
for name in self._schema[const.DISC_INSTANCE_VALUES].keys():
self._values[name] = None
self._schema[const.DISC_VALUES][name] = \
self._schema[const.DISC_INSTANCE_VALUES][name]
self._schema[const.DISC_VALUES][name][const.DISC_INSTANCE] = \
[primary_value.instance]
self._values[const.DISC_PRIMARY] = primary_value
self._node = primary_value.node
self._schema[const.DISC_NODE_ID] = [self._node.node_id]
# Check values that have already been discovered for node
for value in self._node.values.values():
self.check_value(value)
self._check_entity_ready()
def __getattr__(self, name):
"""Get the specified value for this entity."""
return self._values[name]
def __iter__(self):
"""Allow iteration over all values."""
return iter(self._values.values())
def check_value(self, value):
"""Check if the new value matches a missing value for this entity.
If a match is found, it is added to the values mapping.
"""
if not check_node_schema(value.node, self._schema):
return
for name in self._values:
if self._values[name] is not None:
continue
if not check_value_schema(
value, self._schema[const.DISC_VALUES][name]):
continue
self._values[name] = value
if self._entity:
self._entity.value_changed()
self._check_entity_ready()
def _check_entity_ready(self):
"""Check if all required values are discovered and create entity."""
if self._workaround_ignore:
return
if self._entity is not None:
return
for name in self._schema[const.DISC_VALUES]:
if self._values[name] is None and \
not self._schema[const.DISC_VALUES][name].get(
const.DISC_OPTIONAL):
return
component = self._schema[const.DISC_COMPONENT]
workaround_component = workaround.get_device_component_mapping(
self.primary)
if workaround_component and workaround_component != component:
if workaround_component == workaround.WORKAROUND_IGNORE:
_LOGGER.info("Ignoring device %s due to workaround.",
"{}.{}".format(
component, object_id(self.primary)))
# No entity will be created for this value
self._workaround_ignore = True
return
_LOGGER.debug("Using %s instead of %s",
workaround_component, component)
component = workaround_component
name = "{}.{}".format(component, object_id(self.primary))
node_config = self._device_config.get(name)
# Configure node
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, "
"Specific_command_class=%s, "
"Command_class=%s, Value type=%s, "
"Genre=%s as %s", self._node.node_id,
self._node.generic, self._node.specific,
self.primary.command_class, self.primary.type,
self.primary.genre, component)
if node_config.get(CONF_IGNORED):
_LOGGER.info(
"Ignoring node %s due to device settings.", self._node.node_id)
# No entity will be created for this value
self._workaround_ignore = True
return
polling_intensity = convert(
node_config.get(CONF_POLLING_INTENSITY), int)
if polling_intensity:
self.primary.enable_poll(polling_intensity)
else:
self.primary.disable_poll()
platform = get_platform(component, DOMAIN)
device = platform.get_device(
node=self._node, values=self,
node_config=node_config, hass=self._hass)
if not device:
# No entity will be created for this value
self._workaround_ignore = True
return
self._entity = device
dict_id = self.primary.value_id
@asyncio.coroutine
def discover_device(component, device, dict_id):
"""Put device in a dictionary and call discovery on it."""
self._hass.data[DATA_ZWAVE_DICT][dict_id] = device
yield from discovery.async_load_platform(
self._hass, component, DOMAIN,
{const.DISCOVERY_DEVICE: dict_id}, self._zwave_config)
self._hass.add_job(discover_device, component, device, dict_id)
class ZWaveDeviceEntity(Entity): class ZWaveDeviceEntity(Entity):
"""Representation of a Z-Wave node entity.""" """Representation of a Z-Wave node entity."""
def __init__(self, value, domain): def __init__(self, values, domain):
"""Initialize the z-Wave device.""" """Initialize the z-Wave device."""
# pylint: disable=import-error # pylint: disable=import-error
from openzwave.network import ZWaveNetwork from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher from pydispatch import dispatcher
self._value = value self.values = values
self._value.set_change_verified(False) self.node = values.primary.node
self.entity_id = "{}.{}".format(domain, object_id(value)) self.values.primary.set_change_verified(False)
self.entity_id = "{}.{}".format(domain, object_id(values.primary))
self._name = _value_name(self._value) self._name = _value_name(self.values.primary)
self._unique_id = "ZWAVE-{}-{}".format(self._value.node.node_id, self._unique_id = "ZWAVE-{}-{}".format(self.node.node_id,
self._value.object_id) self.values.primary.object_id)
self._wakeup_value_id = None
self._battery_value_id = None
self._power_value_id = None
self._update_scheduled = False self._update_scheduled = False
self._update_attributes() self._update_attributes()
@ -778,19 +773,11 @@ class ZWaveDeviceEntity(Entity):
def network_value_changed(self, value): def network_value_changed(self, value):
"""Called when a value has changed on the network.""" """Called when a value has changed on the network."""
if self._value.value_id == value.value_id: if value.value_id in [v.value_id for v in self.values if v]:
return self.value_changed()
dependent_ids = self._get_dependent_value_ids()
if dependent_ids is None and self._value.node == value.node:
return self.value_changed()
if dependent_ids is not None and value.value_id in dependent_ids:
return self.value_changed() return self.value_changed()
def value_changed(self): def value_changed(self):
"""Called when a value for this entity's node has changed.""" """Called when a value for this entity's node has changed."""
if not self._value.node.is_ready:
self._update_ids()
self._update_attributes() self._update_attributes()
self.update_properties() self.update_properties()
# If value changed after device was created but before setup_platform # If value changed after device was created but before setup_platform
@ -798,29 +785,6 @@ class ZWaveDeviceEntity(Entity):
if self.hass and not self._update_scheduled: if self.hass and not self._update_scheduled:
self.hass.add_job(self._schedule_update) self.hass.add_job(self._schedule_update)
def _update_ids(self):
"""Update value_ids from which to pull attributes."""
if self._wakeup_value_id is None:
self._wakeup_value_id = self.get_value(
class_id=const.COMMAND_CLASS_WAKE_UP, member='value_id')
if self._battery_value_id is None:
self._battery_value_id = self.get_value(
class_id=const.COMMAND_CLASS_BATTERY, member='value_id')
if self._power_value_id is None:
self._power_value_id = self.get_value(
class_id=[const.COMMAND_CLASS_SENSOR_MULTILEVEL,
const.COMMAND_CLASS_METER],
label=['Power'], member='value_id',
instance=self._value.instance)
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on.
None if depends on the whole node.
"""
return []
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Add device to dict.""" """Add device to dict."""
@ -829,45 +793,26 @@ class ZWaveDeviceEntity(Entity):
SIGNAL_REFRESH_ENTITY_FORMAT.format(self.entity_id), SIGNAL_REFRESH_ENTITY_FORMAT.format(self.entity_id),
self.refresh_from_network) self.refresh_from_network)
def _get_dependent_value_ids(self):
"""Return a list of value_ids this device depend on.
Return None if it depends on the whole node.
"""
if self.dependent_value_ids is None:
# Device depends on node.
return None
if not self._value.node.is_ready:
# Node is not ready, so depend on the whole node.
return None
return [val for val in (self.dependent_value_ids + [
self._wakeup_value_id, self._battery_value_id,
self._power_value_id]) if val]
def _update_attributes(self): def _update_attributes(self):
"""Update the node attributes. May only be used inside callback.""" """Update the node attributes. May only be used inside callback."""
self.node_id = self._value.node.node_id self.node_id = self.node.node_id
self.location = self._value.node.location self.location = self.node.location
self.battery_level = self._value.node.get_battery_level(
self._battery_value_id)
self.wakeup_interval = None
if self._wakeup_value_id:
self.wakeup_interval = self._value.node.values[
self._wakeup_value_id].data
power_value = None
if self._power_value_id:
power_value = self._value.node.values[self._power_value_id]
self.power_consumption = round(
power_value.data, power_value.precision) if power_value else None
def get_value(self, **kwargs): if self.values.battery:
"""Simplifyer to get values. May only be used inside callback.""" self.battery_level = self.values.battery.data
return value_handler(self._value, method='get', **kwargs) else:
self.battery_level = None
def set_value(self, **kwargs): if self.values.wakeup:
"""Simplifyer to set a value.""" self.wakeup_interval = self.values.wakeup.data
return value_handler(self._value, method='set', **kwargs) else:
self.wakeup_interval = None
if self.values.power:
self.power_consumption = round(
self.values.power.data, self.values.power.precision)
else:
self.power_consumption = None
def update_properties(self): def update_properties(self):
"""Callback on data changes for node values.""" """Callback on data changes for node values."""
@ -911,13 +856,8 @@ class ZWaveDeviceEntity(Entity):
def refresh_from_network(self): def refresh_from_network(self):
"""Refresh all dependent values from zwave network.""" """Refresh all dependent values from zwave network."""
dependent_ids = self._get_dependent_value_ids() for value in self.values:
if dependent_ids is None: self.node.refresh_value(value.value_id)
# Entity depends on the whole node
self._value.node.refresh_info()
return
for value_id in dependent_ids + [self._value.value_id]:
self._value.node.refresh_value(value_id)
@callback @callback
def _schedule_update(self): def _schedule_update(self):

View File

@ -311,3 +311,22 @@ TYPE_BOOL = "Bool"
TYPE_DECIMAL = "Decimal" TYPE_DECIMAL = "Decimal"
TYPE_INT = "Int" TYPE_INT = "Int"
TYPE_LIST = "List" TYPE_LIST = "List"
TYPE_STRING = "String"
DISC_COMMAND_CLASS = "command_class"
DISC_COMPONENT = "component"
DISC_GENERIC_DEVICE_CLASS = "generic_device_class"
DISC_GENRE = "genre"
DISC_INDEX = "index"
DISC_INSTANCE_VALUES = "instance_values"
DISC_INSTANCE = "instance"
DISC_LABEL = "label"
DISC_NODE_ID = "node_id"
DISC_NODE_VALUES = "node_values"
DISC_OPTIONAL = "optional"
DISC_PRIMARY = "primary"
DISC_READONLY = "readonly"
DISC_SPECIFIC_DEVICE_CLASS = "specific_device_class"
DISC_TYPE = "type"
DISC_VALUES = "values"
DISC_WRITEONLY = "writeonly"

View File

@ -0,0 +1,220 @@
"""Zwave discovery schemas."""
from . import const
DEFAULT_NODE_VALUES_SCHEMA = {
'wakeup': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_WAKE_UP],
const.DISC_GENRE: const.GENRE_USER,
const.DISC_OPTIONAL: True,
},
'battery': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_BATTERY],
const.DISC_OPTIONAL: True,
},
}
DEFAULT_INSTANCE_VALUES_SCHEMA = {
'power': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL,
const.COMMAND_CLASS_METER],
const.DISC_LABEL: ['Power'],
const.DISC_OPTIONAL: True,
},
}
DISCOVERY_SCHEMAS = [
{const.DISC_COMPONENT: 'binary_sensor',
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_SENSOR_ALARM,
const.GENERIC_TYPE_SENSOR_BINARY,
const.GENERIC_TYPE_SWITCH_BINARY,
const.GENERIC_TYPE_METER,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
const.GENERIC_TYPE_THERMOSTAT],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_BINARY],
const.DISC_TYPE: const.TYPE_BOOL,
const.DISC_GENRE: const.GENRE_USER
}})},
{const.DISC_COMPONENT: 'climate',
const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_THERMOSTAT],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_SETPOINT],
},
'temperature': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL],
const.DISC_LABEL: 'Temperature',
const.DISC_OPTIONAL: True,
},
'mode': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_MODE],
const.DISC_OPTIONAL: True,
},
'fan_mode': {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_FAN_MODE],
const.DISC_OPTIONAL: True,
},
'operating_state': {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE],
const.DISC_OPTIONAL: True,
},
'fan_state': {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_FAN_STATE],
const.DISC_OPTIONAL: True,
},
'zxt_120_swing_mode': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_CONFIGURATION],
const.DISC_INDEX: [33],
const.DISC_OPTIONAL: True,
}})},
{const.DISC_COMPONENT: 'cover', # Rollershutter
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_ENTRY_CONTROL],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL,
const.SPECIFIC_TYPE_MOTOR_MULTIPOSITION,
const.SPECIFIC_TYPE_SECURE_BARRIER_ADDON,
const.SPECIFIC_TYPE_SECURE_DOOR],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.DISC_GENRE: const.GENRE_USER,
},
'open': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.DISC_LABEL: ['Open', 'Up'],
const.DISC_OPTIONAL: True,
},
'close': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.DISC_LABEL: ['Close', 'Down'],
const.DISC_OPTIONAL: True,
}})},
{const.DISC_COMPONENT: 'cover', # Garage Door
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_ENTRY_CONTROL],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL,
const.SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL,
const.SPECIFIC_TYPE_MOTOR_MULTIPOSITION,
const.SPECIFIC_TYPE_SECURE_BARRIER_ADDON,
const.SPECIFIC_TYPE_SECURE_DOOR],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_BARRIER_OPERATOR,
const.COMMAND_CLASS_SWITCH_BINARY],
const.DISC_GENRE: const.GENRE_USER,
}})},
{const.DISC_COMPONENT: 'light',
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_REMOTE],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL,
const.SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL,
const.SPECIFIC_TYPE_NOT_USED],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_MULTILEVEL],
const.DISC_GENRE: const.GENRE_USER,
const.DISC_TYPE: const.TYPE_BYTE,
},
'color': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_COLOR],
const.DISC_GENRE: const.GENRE_USER,
const.DISC_TYPE: const.TYPE_STRING,
const.DISC_READONLY: False,
const.DISC_WRITEONLY: False,
const.DISC_OPTIONAL: True,
},
'color_channels': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_COLOR],
const.DISC_GENRE: const.GENRE_SYSTEM,
const.DISC_TYPE: const.TYPE_INT,
const.DISC_OPTIONAL: True,
}})},
{const.DISC_COMPONENT: 'lock',
const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_ENTRY_CONTROL],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_ADVANCED_DOOR_LOCK,
const.SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_DOOR_LOCK],
const.DISC_TYPE: const.TYPE_BOOL,
const.DISC_GENRE: const.GENRE_USER,
},
'access_control': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_ALARM],
const.DISC_LABEL: 'Access Control',
const.DISC_OPTIONAL: True,
},
'alarm_type': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_ALARM],
const.DISC_LABEL: 'Alarm Type',
const.DISC_OPTIONAL: True,
},
'alarm_level': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_ALARM],
const.DISC_LABEL: 'Alarm Level',
const.DISC_OPTIONAL: True,
},
'v2btze_advanced': {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_CONFIGURATION],
const.DISC_INDEX: [12],
const.DISC_LABEL: 'Access Control',
const.DISC_OPTIONAL: True,
}})},
{const.DISC_COMPONENT: 'sensor',
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_SENSOR_MULTILEVEL,
const.COMMAND_CLASS_METER,
const.COMMAND_CLASS_ALARM,
const.COMMAND_CLASS_SENSOR_ALARM,
const.COMMAND_CLASS_BATTERY],
const.DISC_GENRE: const.GENRE_USER,
}})},
{const.DISC_COMPONENT: 'switch',
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_SENSOR_ALARM,
const.GENERIC_TYPE_SENSOR_BINARY,
const.GENERIC_TYPE_SWITCH_BINARY,
const.GENERIC_TYPE_ENTRY_CONTROL,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
const.GENERIC_TYPE_GENERIC_CONTROLLER,
const.GENERIC_TYPE_SWITCH_REMOTE,
const.GENERIC_TYPE_REPEATER_SLAVE,
const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_WALL_CONTROLLER],
const.DISC_NODE_VALUES: dict(DEFAULT_NODE_VALUES_SCHEMA),
const.DISC_INSTANCE_VALUES: dict(DEFAULT_INSTANCE_VALUES_SCHEMA, **{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_BINARY],
const.DISC_TYPE: const.TYPE_BOOL,
const.DISC_GENRE: const.GENRE_USER,
}})},
]

View File

@ -1,54 +1,71 @@
"""Zwave util methods.""" """Zwave util methods."""
import logging import logging
from . import const
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def value_handler(value, method=None, class_id=None, index=None, def check_node_schema(node, schema):
label=None, data=None, member=None, instance=None, """Check if node matches the passed node schema."""
**kwargs): if (const.DISC_NODE_ID in schema and
"""Get the values for a given command_class with arguments. node.node_id not in schema[const.DISC_NODE_ID]):
_LOGGER.debug("node.node_id %s not in node_id %s",
node.node_id, schema[const.DISC_NODE_ID])
return False
if (const.DISC_GENERIC_DEVICE_CLASS in schema and
node.generic not in schema[const.DISC_GENERIC_DEVICE_CLASS]):
_LOGGER.debug("node.generic %s not in generic_device_class %s",
node.generic, schema[const.DISC_GENERIC_DEVICE_CLASS])
return False
if (const.DISC_SPECIFIC_DEVICE_CLASS in schema and
node.specific not in schema[const.DISC_SPECIFIC_DEVICE_CLASS]):
_LOGGER.debug("node.specific %s not in specific_device_class %s",
node.specific, schema[const.DISC_SPECIFIC_DEVICE_CLASS])
return False
return True
May only be used inside callback.
""" def check_value_schema(value, schema):
values = [] """Check if the value matches the passed value schema."""
if class_id is None: if (const.DISC_COMMAND_CLASS in schema and
values.extend(value.node.get_values(**kwargs).values()) value.command_class not in schema[const.DISC_COMMAND_CLASS]):
else: _LOGGER.debug("value.command_class %s not in command_class %s",
if not isinstance(class_id, list): value.command_class, schema[const.DISC_COMMAND_CLASS])
class_id = [class_id] return False
for cid in class_id: if (const.DISC_TYPE in schema and
values.extend(value.node.get_values( value.type not in schema[const.DISC_TYPE]):
class_id=cid, **kwargs).values()) _LOGGER.debug("value.type %s not in type %s",
_LOGGER.debug('method=%s, class_id=%s, index=%s, label=%s, data=%s,' value.type, schema[const.DISC_TYPE])
' member=%s, instance=%d, kwargs=%s', return False
method, class_id, index, label, data, member, instance, if (const.DISC_GENRE in schema and
kwargs) value.genre not in schema[const.DISC_GENRE]):
_LOGGER.debug('values=%s', values) _LOGGER.debug("value.genre %s not in genre %s",
results = None value.genre, schema[const.DISC_GENRE])
for value in values: return False
if index is not None and value.index != index: if (const.DISC_READONLY in schema and
continue value.is_read_only is not schema[const.DISC_READONLY]):
if label is not None: _LOGGER.debug("value.is_read_only %s not %s",
label_found = False value.is_read_only, schema[const.DISC_READONLY])
for entry in label: return False
if value.label == entry: if (const.DISC_WRITEONLY in schema and
label_found = True value.is_write_only is not schema[const.DISC_WRITEONLY]):
break _LOGGER.debug("value.is_write_only %s not %s",
if not label_found: value.is_write_only, schema[const.DISC_WRITEONLY])
continue return False
if method == 'set': if (const.DISC_LABEL in schema and
value.data = data value.label not in schema[const.DISC_LABEL]):
return _LOGGER.debug("value.label %s not in label %s",
if data is not None and value.data != data: value.label, schema[const.DISC_LABEL])
continue return False
if instance is not None and value.instance != instance: if (const.DISC_INDEX in schema and
continue value.index not in schema[const.DISC_INDEX]):
if member is not None: _LOGGER.debug("value.index %s not in index %s",
results = getattr(value, member) value.index, schema[const.DISC_INDEX])
else: return False
results = value if (const.DISC_INSTANCE in schema and
break value.instance not in schema[const.DISC_INSTANCE]):
_LOGGER.debug('final result=%s', results) _LOGGER.debug("value.instance %s not in instance %s",
return results value.instance, schema[const.DISC_INSTANCE])
return False
return True