diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index e0ff8535cfd..5fd9c39ef2a 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -19,34 +19,34 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] -def get_device(value, **kwargs): +def get_device(values, **kwargs): """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: # Default the multiplier to 4 - re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4) - return ZWaveTriggerSensor(value, "motion", re_arm_multiplier * 8) + re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4 + return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8) - if workaround.get_device_component_mapping(value) == DOMAIN: - return ZWaveBinarySensor(value, None) + if workaround.get_device_component_mapping(values.primary) == DOMAIN: + return ZWaveBinarySensor(values, None) - if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY: - return ZWaveBinarySensor(value, None) + if values.primary.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY: + return ZWaveBinarySensor(values, None) return None class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): """Representation of a binary sensor within Z-Wave.""" - def __init__(self, value, device_class): + def __init__(self, values, device_class): """Initialize the sensor.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._sensor_type = device_class - self._state = self._value.data + self._state = self.values.primary.data def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data + self._state = self.values.primary.data @property def is_on(self): @@ -62,15 +62,15 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): class ZWaveTriggerSensor(ZWaveBinarySensor): """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.""" - super(ZWaveTriggerSensor, self).__init__(value, device_class) + super(ZWaveTriggerSensor, self).__init__(values, device_class) self.re_arm_sec = re_arm_sec self.invalidate_after = None def update_properties(self): """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 if not self.hass: return diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 660eb76098d..4d625d78b62 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -10,7 +10,6 @@ import logging from homeassistant.components.climate import DOMAIN from homeassistant.components.climate import ClimateDevice 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.const import ( 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.""" temp_unit = hass.config.units.temperature_unit - return ZWaveClimate(value, temp_unit) + return ZWaveClimate(values, temp_unit) class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """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.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) - self._index = value.index - self._node = value.node + ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._target_temperature = None self._current_temperature = None self._current_operation = None @@ -61,10 +58,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): _LOGGER.debug("temp_unit is %s", self._unit) self._zxt_120 = None # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16)) + if (self.node.manufacturer_id.strip() and + self.node.product_id.strip()): + specific_sensor_key = ( + int(self.node.manufacturer_id, 16), + int(self.node.product_id, 16)) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat" @@ -75,81 +73,58 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def update_properties(self): """Callback on data changes for node values.""" # Operation Mode - self._current_operation = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data') - operation_list = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, - member='data_items') - if operation_list: - self._operation_list = list(operation_list) + if self.values.mode: + self._current_operation = self.values.mode.data + operation_list = self.values.mode.data_items + if operation_list: + self._operation_list = list(operation_list) _LOGGER.debug("self._operation_list=%s", self._operation_list) _LOGGER.debug("self._current_operation=%s", self._current_operation) # Current Temp - self._current_temperature = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, - label=['Temperature'], member='data') - device_unit = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, - label=['Temperature'], member='units') - if device_unit is not None: - self._unit = device_unit + if self.values.temperature: + self._current_temperature = self.values.temperature.data + device_unit = self.values.temperature.units + if device_unit is not None: + self._unit = device_unit # Fan Mode - self._current_fan_mode = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - member='data') - fan_list = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - member='data_items') - if fan_list: - self._fan_list = list(fan_list) + if self.values.fan_mode: + self._current_fan_mode = self.values.fan_mode.data + fan_list = self.values.fan_mode.data_items + if fan_list: + self._fan_list = list(fan_list) _LOGGER.debug("self._fan_list=%s", self._fan_list) _LOGGER.debug("self._current_fan_mode=%s", self._current_fan_mode) # Swing mode if self._zxt_120 == 1: - self._current_swing_mode = ( - self.get_value( - class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, - index=33, - member='data')) - 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) + if self.values.zxt_120_swing_mode: + self._current_swing_mode = self.values.zxt_120_swing_mode.data + swing_list = self.values.zxt_120_swing_mode.data_items + if swing_list: + self._swing_list = list(swing_list) _LOGGER.debug("self._swing_list=%s", self._swing_list) _LOGGER.debug("self._current_swing_mode=%s", self._current_swing_mode) # Set point - temps = [] - for value in ( - self._node.get_values( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT) - .values()): - temps.append((round(float(value.data)), 1)) - if value.index == self._index: - if value.data == 0: - _LOGGER.debug("Setpoint is 0, setting default to " - "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) + if self.values.primary.data == 0: + _LOGGER.debug("Setpoint is 0, setting default to " + "current_temperature=%s", + self._current_temperature) + self._target_temperature = ( + round((float(self._current_temperature)), 1)) + else: + self._target_temperature = round( + (float(self.values.primary.data)), 1) # Operating state - self._operating_state = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, - member='data') + if self.values.operating_state: + self._operating_state = self.values.operating_state.data # Fan operating state - self._fan_state = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE, - member='data') + if self.values.fan_state: + self._fan_state = self.values.fan_state.data @property def should_poll(self): @@ -213,29 +188,24 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): else: return - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT, - index=self._index, data=temperature) + self.values.primary.data = temperature self.schedule_update_ha_state() def set_fan_mode(self, fan): """Set new target fan mode.""" - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - index=0, data=bytes(fan, 'utf-8')) + if self.values.fan_mode: + self.values.fan_mode.data = bytes(fan, 'utf-8') def set_operation_mode(self, operation_mode): """Set new target operation mode.""" - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, - index=0, data=bytes(operation_mode, 'utf-8')) + if self.values.mode: + self.values.mode.data = bytes(operation_mode, 'utf-8') def set_swing_mode(self, swing_mode): """Set new target swing mode.""" if self._zxt_120 == 1: - self.set_value( - class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, - index=33, data=bytes(swing_mode, 'utf-8')) + if self.values.zxt_120_swing_mode: + self.values.zxt_120_swing_mode = bytes(swing_mode, 'utf-8') @property def device_state_attributes(self): @@ -246,8 +216,3 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if self._fan_state: data[ATTR_FAN_STATE] = self._fan_state return data - - @property - def dependent_value_ids(self): - """List of value IDs a device depends on.""" - return None diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index 46f23a68515..129dbd32ffe 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -20,64 +20,49 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -def get_device(value, **kwargs): +def get_device(values, **kwargs): """Create zwave entity device.""" - if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL - and value.index == 0): - return ZwaveRollershutter(value) - elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or - value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR): - return ZwaveGarageDoor(value) + if (values.primary.command_class == + zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL + and values.primary.index == 0): + return ZwaveRollershutter(values) + elif (values.primary.command_class in [ + zwave.const.COMMAND_CLASS_SWITCH_BINARY, + zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]): + return ZwaveGarageDoor(values) return None class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): """Representation of an Zwave roller shutter.""" - def __init__(self, value): + def __init__(self, values): """Initialize the zwave rollershutter.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) + ZWaveDeviceEntity.__init__(self, values, DOMAIN) # pylint: disable=no-member - self._node = value.node self._open_id = None self._close_id = None - self._current_position_id = None self._current_position = None - self._workaround = workaround.get_device_mapping(value) + self._workaround = workaround.get_device_mapping(values.primary) if self._workaround: _LOGGER.debug("Using workaround %s", self._workaround) 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): """Callback on data changes for node values.""" # Position value - if not self._node.is_ready: - if self._current_position_id is None: - self._current_position_id = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, - label=['Level'], member='value_id') - if self._open_id is None: - self._open_id = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, - label=['Open', 'Up'], member='value_id') - if self._close_id is None: - self._close_id = self.get_value( - 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) + self._current_position = self.values.primary.data + + if self.values.open and self.values.close and \ + self._open_id is None and self._close_id is None: + if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE: + self._open_id = self.values.close.value_id + self._close_id = self.values.open.value_id + self._workaround = None + else: + self._open_id = self.values.open.value_id + self._close_id = self.values.close.value_id @property def is_closed(self): @@ -112,7 +97,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): def set_cover_position(self, position, **kwargs): """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): """Stop the roller shutter.""" @@ -122,14 +107,14 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): """Representation of an Zwave garage door device.""" - def __init__(self, value): + def __init__(self, values): """Initialize the zwave garage door.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) + ZWaveDeviceEntity.__init__(self, values, DOMAIN) self.update_properties() def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data + self._state = self.values.primary.data @property def is_closed(self): @@ -138,11 +123,11 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): def close_cover(self): """Close the garage door.""" - self._value.data = False + self.values.primary.data = False def open_cover(self): """Open the garage door.""" - self._value.data = True + self.values.primary.data = True @property def device_class(self): diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 59df6d8a745..c2357787999 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -49,9 +49,9 @@ SUPPORT_ZWAVE_COLORTEMP = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR | SUPPORT_COLOR_TEMP) -def get_device(node, value, node_config, **kwargs): +def get_device(node, values, node_config, **kwargs): """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) delay = node_config.get(zwave.CONF_REFRESH_DELAY) _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) if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR): - return ZwaveColorLight(value, refresh, delay) + return ZwaveColorLight(values, refresh, delay) else: - return ZwaveDimmer(value, refresh, delay) + return ZwaveDimmer(values, refresh, delay) def brightness_state(value): @@ -75,9 +75,9 @@ def brightness_state(value): class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): """Representation of a Z-Wave dimmer.""" - def __init__(self, value, refresh, delay): + def __init__(self, values, refresh, delay): """Initialize the light.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._brightness = None self._state = None self._delay = delay @@ -86,10 +86,10 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): # Enable appropriate workaround flags for our device # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16)) + if (self.node.manufacturer_id.strip() and + self.node.product_id.strip()): + specific_sensor_key = (int(self.node.manufacturer_id, 16), + int(self.node.product_id, 16)) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: _LOGGER.debug("AEOTEC ZW098 workaround enabled") @@ -105,7 +105,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): def update_properties(self): """Update internal properties based on zwave values.""" # Brightness - self._brightness, self._state = brightness_state(self._value) + self._brightness, self._state = brightness_state(self.values.primary) def value_changed(self): """Called when a value for this entity's node has changed.""" @@ -116,7 +116,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): def _refresh_value(): """Used timer callback for delayed value refresh.""" self._refreshing = True - self._value.refresh() + self.values.primary.refresh() if self._timer is not None and self._timer.isAlive(): self._timer.cancel() @@ -151,12 +151,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): else: 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 def turn_off(self, **kwargs): """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 @@ -170,73 +170,28 @@ def ct_to_rgb(temp): class ZwaveColorLight(ZwaveDimmer): """Representation of a Z-Wave color changing light.""" - def __init__(self, value, refresh, delay): + def __init__(self, values, refresh, delay): """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._rgb = None self._ct = None - super().__init__(value, 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() + super().__init__(values, refresh, delay) def update_properties(self): """Update internal properties based on zwave values.""" super().update_properties() - if self._value_color is None: + if self.values.color is None: return - if self._value_color_channels is None: + if self.values.color_channels is None: return # Color Channels - self._color_channels = self._value_color_channels.data + self._color_channels = self.values.color_channels.data # Color Data String - data = self._value_color.data + data = self.values.color.data # RGB is always present in the openzwave color data string. self._rgb = [ @@ -329,8 +284,8 @@ class ZwaveColorLight(ZwaveDimmer): rgbw += format(colorval, '02x').encode('utf-8') rgbw += b'0000' - if rgbw and self._value_color: - self._value_color.node.set_rgbw(self._value_color.value_id, rgbw) + if rgbw and self.values.color: + self.values.color.data = rgbw super().turn_on(**kwargs) diff --git a/homeassistant/components/lock/zwave.py b/homeassistant/components/lock/zwave.py index cfafe955e2c..55e5b6276a6 100644 --- a/homeassistant/components/lock/zwave.py +++ b/homeassistant/components/lock/zwave.py @@ -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.""" descriptions = load_yaml_config_file( path.join(path.dirname(__file__), 'services.yaml')) @@ -191,16 +191,15 @@ def get_device(hass, node, value, **kwargs): clear_usercode, descriptions.get(SERVICE_CLEAR_USERCODE), schema=CLEAR_USERCODE_SCHEMA) - return ZwaveLock(value) + return ZwaveLock(values) class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): """Representation of a Z-Wave Lock.""" - def __init__(self, value): + def __init__(self, values): """Initialize the Z-Wave lock device.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) - self._node = value.node + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._state = None self._notification = None self._lock_status = None @@ -208,10 +207,10 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): # Enable appropriate workaround flags for our device # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16)) + if (self.node.manufacturer_id.strip() and + self.node.product_id.strip()): + specific_sensor_key = (int(self.node.manufacturer_id, 16), + int(self.node.product_id, 16)) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_V2BTZE: self._v2btze = 1 @@ -221,36 +220,33 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): def update_properties(self): """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' ' is %s', self._state) - notification_data = self.get_value(class_id=zwave.const - .COMMAND_CLASS_ALARM, - label=['Access Control'], - member='data') - if notification_data: + if self.values.access_control: + notification_data = self.values.access_control.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 - .COMMAND_CLASS_ALARM, - label=['Alarm Type'], member='data') + if self._v2btze: + if self.values.v2btze_advanced and \ + 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)) - alarm_level = self.get_value(class_id=zwave.const - .COMMAND_CLASS_ALARM, - label=['Alarm Level'], member='data') + if self.values.alarm_level: + alarm_level = self.values.alarm_level.data + else: + alarm_level = None _LOGGER.debug('Lock alarm_level is %s', str(alarm_level)) + if not alarm_type: return if alarm_type is 21: @@ -277,11 +273,11 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): def lock(self, **kwargs): """Lock the device.""" - self._value.data = True + self.values.primary.data = True def unlock(self, **kwargs): """Unlock the device.""" - self._value.data = False + self.values.primary.data = False @property def device_state_attributes(self): @@ -292,8 +288,3 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): if self._lock_status: data[ATTR_LOCK_STATUS] = self._lock_status return data - - @property - def dependent_value_ids(self): - """List of value IDs a device depends on.""" - return None diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 0c4d61d86d2..25371d1ca78 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -15,34 +15,34 @@ from homeassistant.components.zwave import async_setup_platform # noqa # pylint _LOGGER = logging.getLogger(__name__) -def get_device(node, value, **kwargs): +def get_device(node, values, **kwargs): """Create zwave entity device.""" # Generic Device mappings - if value.command_class == zwave.const.COMMAND_CLASS_BATTERY: - return ZWaveSensor(value) + if values.primary.command_class == zwave.const.COMMAND_CLASS_BATTERY: + return ZWaveSensor(values) 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 \ - value.type == zwave.const.TYPE_DECIMAL: - return ZWaveMultilevelSensor(value) + values.primary.type == zwave.const.TYPE_DECIMAL: + return ZWaveMultilevelSensor(values) if node.has_command_class(zwave.const.COMMAND_CLASS_ALARM) or \ node.has_command_class(zwave.const.COMMAND_CLASS_SENSOR_ALARM): - return ZWaveAlarmSensor(value) + return ZWaveAlarmSensor(values) return None class ZWaveSensor(zwave.ZWaveDeviceEntity): """Representation of a Z-Wave sensor.""" - def __init__(self, value): + def __init__(self, values): """Initialize the sensor.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self.update_properties() def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data - self._units = self._value.units + self._state = self.values.primary.data + self._units = self.values.primary.units @property def force_update(self): diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index bbae1c1c68c..ba64785013f 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -15,29 +15,30 @@ from homeassistant.components.zwave import workaround, async_setup_platform # n _LOGGER = logging.getLogger(__name__) -def get_device(value, **kwargs): +def get_device(values, **kwargs): """Create zwave entity device.""" - return ZwaveSwitch(value) + return ZwaveSwitch(values) class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice): """Representation of a Z-Wave switch.""" - def __init__(self, value): + def __init__(self, values): """Initialize the Z-Wave switch device.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) - self.refresh_on_update = (workaround.get_device_mapping(value) == - workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE) + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) + self.refresh_on_update = ( + workaround.get_device_mapping(values.primary) == + workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE) self.last_update = time.perf_counter() - self._state = self._value.data + self._state = self.values.primary.data def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data + self._state = self.values.primary.data if self.refresh_on_update and \ time.perf_counter() - self.last_update > 30: self.last_update = time.perf_counter() - self._value.node.request_state() + self.node.request_state() @property def is_on(self): @@ -46,8 +47,8 @@ class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice): def turn_on(self, **kwargs): """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): """Turn the device off.""" - self._value.node.set_switch(self._value.value_id, False) + self.node.set_switch(self.values.primary.value_id, False) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 5eadd086289..5113e312efc 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/zwave/ """ import asyncio +import copy import logging import os.path import time @@ -29,7 +30,8 @@ from homeassistant.helpers.dispatcher import ( from . import const 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'] @@ -64,88 +66,6 @@ DATA_ZWAVE_DICT = 'zwave_devices' 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({ vol.Required(ATTR_ENTITY_ID): cv.entity_id, vol.Required(const.ATTR_NAME): cv.string, @@ -358,92 +278,25 @@ def setup(hass, config): dispatcher.connect(log_all, weak=False) + discovered_values = [] + def value_added(node, value): """Called when a value is added to a node on the network.""" - for (component, - generic_device_class, - specific_device_class, - command_class, - value_type, - value_genre) in DISCOVERY_COMPONENTS: + # Check if this value should be tracked by an existing entity + for values in discovered_values: + values.check_value(value) - _LOGGER.debug("Component=%s Node_id=%s query start", - component, node.node_id) - 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) + for schema in DISCOVERY_SCHEMAS: + if not check_node_schema(node, schema): continue - if node.specific not in specific_device_class and \ - None not in specific_device_class: - _LOGGER.debug("node.specific %s is not None and in " - "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) + if not check_value_schema( + value, + schema[const.DISC_INSTANCE_VALUES][const.DISC_PRIMARY]): continue - # 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", 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 = "{}.{}".format(component, 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) + values = ZWaveDeviceEntityValues( + hass, schema, value, config, device_config) + discovered_values.append(values) def scene_activated(node, scene_id): """Called when a scene is activated on any node in the network.""" @@ -752,24 +605,166 @@ def setup(hass, config): 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 = id(self) + + @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): """Representation of a Z-Wave node entity.""" - def __init__(self, value, domain): + def __init__(self, values, domain): """Initialize the z-Wave device.""" # pylint: disable=import-error from openzwave.network import ZWaveNetwork from pydispatch import dispatcher - self._value = value - self._value.set_change_verified(False) - self.entity_id = "{}.{}".format(domain, object_id(value)) + self.values = values + self.node = values.primary.node + self.values.primary.set_change_verified(False) + self.entity_id = "{}.{}".format(domain, object_id(values.primary)) - self._name = _value_name(self._value) - self._unique_id = "ZWAVE-{}-{}".format(self._value.node.node_id, - self._value.object_id) - self._wakeup_value_id = None - self._battery_value_id = None - self._power_value_id = None + self._name = _value_name(self.values.primary) + self._unique_id = "ZWAVE-{}-{}".format(self.node.node_id, + self.values.primary.object_id) self._update_scheduled = False self._update_attributes() @@ -778,19 +773,11 @@ class ZWaveDeviceEntity(Entity): def network_value_changed(self, value): """Called when a value has changed on the network.""" - if self._value.value_id == value.value_id: - 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: + if value.value_id in [v.value_id for v in self.values if v]: return self.value_changed() def value_changed(self): """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_properties() # 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: 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 def async_added_to_hass(self): """Add device to dict.""" @@ -829,45 +793,26 @@ class ZWaveDeviceEntity(Entity): SIGNAL_REFRESH_ENTITY_FORMAT.format(self.entity_id), 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): """Update the node attributes. May only be used inside callback.""" - self.node_id = self._value.node.node_id - self.location = self._value.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 + self.node_id = self.node.node_id + self.location = self.node.location - def get_value(self, **kwargs): - """Simplifyer to get values. May only be used inside callback.""" - return value_handler(self._value, method='get', **kwargs) + if self.values.battery: + self.battery_level = self.values.battery.data + else: + self.battery_level = None - def set_value(self, **kwargs): - """Simplifyer to set a value.""" - return value_handler(self._value, method='set', **kwargs) + if self.values.wakeup: + self.wakeup_interval = self.values.wakeup.data + 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): """Callback on data changes for node values.""" @@ -911,13 +856,8 @@ class ZWaveDeviceEntity(Entity): def refresh_from_network(self): """Refresh all dependent values from zwave network.""" - dependent_ids = self._get_dependent_value_ids() - if dependent_ids is None: - # 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) + for value in self.values: + self.node.refresh_value(value.value_id) @callback def _schedule_update(self): diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index ab4c7604dc4..edcdbab1e71 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -311,3 +311,22 @@ TYPE_BOOL = "Bool" TYPE_DECIMAL = "Decimal" TYPE_INT = "Int" 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" diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py new file mode 100644 index 00000000000..9b4af47000f --- /dev/null +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -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, + }})}, +] diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py index 3ce99dbf1ab..09c14fde80a 100644 --- a/homeassistant/components/zwave/util.py +++ b/homeassistant/components/zwave/util.py @@ -1,54 +1,71 @@ """Zwave util methods.""" import logging +from . import const + _LOGGER = logging.getLogger(__name__) -def value_handler(value, method=None, class_id=None, index=None, - label=None, data=None, member=None, instance=None, - **kwargs): - """Get the values for a given command_class with arguments. +def check_node_schema(node, schema): + """Check if node matches the passed node schema.""" + if (const.DISC_NODE_ID in schema and + 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. - """ - values = [] - if class_id is None: - values.extend(value.node.get_values(**kwargs).values()) - else: - if not isinstance(class_id, list): - class_id = [class_id] - for cid in class_id: - values.extend(value.node.get_values( - class_id=cid, **kwargs).values()) - _LOGGER.debug('method=%s, class_id=%s, index=%s, label=%s, data=%s,' - ' member=%s, instance=%d, kwargs=%s', - method, class_id, index, label, data, member, instance, - kwargs) - _LOGGER.debug('values=%s', values) - results = None - for value in values: - if index is not None and value.index != index: - continue - if label is not None: - label_found = False - for entry in label: - if value.label == entry: - label_found = True - break - if not label_found: - continue - if method == 'set': - value.data = data - return - if data is not None and value.data != data: - continue - if instance is not None and value.instance != instance: - continue - if member is not None: - results = getattr(value, member) - else: - results = value - break - _LOGGER.debug('final result=%s', results) - return results +def check_value_schema(value, schema): + """Check if the value matches the passed value schema.""" + if (const.DISC_COMMAND_CLASS in schema and + value.command_class not in schema[const.DISC_COMMAND_CLASS]): + _LOGGER.debug("value.command_class %s not in command_class %s", + value.command_class, schema[const.DISC_COMMAND_CLASS]) + return False + if (const.DISC_TYPE in schema and + value.type not in schema[const.DISC_TYPE]): + _LOGGER.debug("value.type %s not in type %s", + value.type, schema[const.DISC_TYPE]) + return False + if (const.DISC_GENRE in schema and + value.genre not in schema[const.DISC_GENRE]): + _LOGGER.debug("value.genre %s not in genre %s", + value.genre, schema[const.DISC_GENRE]) + return False + if (const.DISC_READONLY in schema and + value.is_read_only is not schema[const.DISC_READONLY]): + _LOGGER.debug("value.is_read_only %s not %s", + value.is_read_only, schema[const.DISC_READONLY]) + return False + if (const.DISC_WRITEONLY in schema and + value.is_write_only is not schema[const.DISC_WRITEONLY]): + _LOGGER.debug("value.is_write_only %s not %s", + value.is_write_only, schema[const.DISC_WRITEONLY]) + return False + if (const.DISC_LABEL in schema and + value.label not in schema[const.DISC_LABEL]): + _LOGGER.debug("value.label %s not in label %s", + value.label, schema[const.DISC_LABEL]) + return False + if (const.DISC_INDEX in schema and + value.index not in schema[const.DISC_INDEX]): + _LOGGER.debug("value.index %s not in index %s", + value.index, schema[const.DISC_INDEX]) + return False + if (const.DISC_INSTANCE in schema and + value.instance not in schema[const.DISC_INSTANCE]): + _LOGGER.debug("value.instance %s not in instance %s", + value.instance, schema[const.DISC_INSTANCE]) + return False + return True diff --git a/tests/components/binary_sensor/test_zwave.py b/tests/components/binary_sensor/test_zwave.py index 44ef13b262f..13f3934f964 100644 --- a/tests/components/binary_sensor/test_zwave.py +++ b/tests/components/binary_sensor/test_zwave.py @@ -7,15 +7,17 @@ from unittest.mock import patch from homeassistant.components.zwave import const from homeassistant.components.binary_sensor import zwave -from tests.mock.zwave import MockNode, MockValue, value_changed +from tests.mock.zwave import ( + MockNode, MockValue, MockEntityValues, value_changed) def test_get_device_detects_none(mock_openzwave): """Test device is not returned.""" node = MockNode() value = MockValue(data=False, node=node) + values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert device is None @@ -24,8 +26,9 @@ def test_get_device_detects_trigger_sensor(mock_openzwave): node = MockNode( manufacturer_id='013c', product_type='0002', product_id='0002') value = MockValue(data=False, node=node) + values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZWaveTriggerSensor) assert device.device_class == "motion" @@ -35,8 +38,9 @@ def test_get_device_detects_workaround_sensor(mock_openzwave): node = MockNode(manufacturer_id='010f', product_type='0b00') value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SENSOR_ALARM) + values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZWaveBinarySensor) @@ -45,8 +49,9 @@ def test_get_device_detects_sensor(mock_openzwave): node = MockNode() value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SENSOR_BINARY) + values = MockEntityValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZWaveBinarySensor) @@ -55,7 +60,8 @@ def test_binary_sensor_value_changed(mock_openzwave): node = MockNode() value = MockValue(data=False, node=node, command_class=const.COMMAND_CLASS_SENSOR_BINARY) - device = zwave.get_device(node=node, value=value, node_config={}) + values = MockEntityValues(primary=value) + device = zwave.get_device(node=node, values=values, node_config={}) assert not device.is_on @@ -71,7 +77,8 @@ def test_trigger_sensor_value_changed(hass, mock_openzwave): node = MockNode( manufacturer_id='013c', product_type='0002', product_id='0002') value = MockValue(data=False, node=node) - device = zwave.get_device(node=node, value=value, node_config={}) + values = MockEntityValues(primary=value) + device = zwave.get_device(node=node, values=values, node_config={}) assert not device.is_on diff --git a/tests/components/light/test_zwave.py b/tests/components/light/test_zwave.py index 50b9ef21742..40df836cbae 100644 --- a/tests/components/light/test_zwave.py +++ b/tests/components/light/test_zwave.py @@ -2,15 +2,27 @@ from homeassistant.components.zwave import const from homeassistant.components.light import zwave, ATTR_BRIGHTNESS -from tests.mock.zwave import MockNode, MockValue, value_changed +from tests.mock.zwave import ( + MockNode, MockValue, MockEntityValues, value_changed) + + +class MockLightValues(MockEntityValues): + """Mock Z-Wave light values.""" + + def __init__(self, **kwargs): + """Initialize the mock zwave values.""" + self.color = None + self.color_channels = None + super().__init__(**kwargs) def test_get_device_detects_dimmer(mock_openzwave): """Test get_device returns a color light.""" node = MockNode() value = MockValue(data=0, node=node) + values = MockLightValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZwaveDimmer) @@ -18,8 +30,9 @@ def test_get_device_detects_colorlight(mock_openzwave): """Test get_device returns a color light.""" node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR]) value = MockValue(data=0, node=node) + values = MockLightValues(primary=value) - device = zwave.get_device(node=node, value=value, node_config={}) + device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZwaveColorLight) @@ -27,7 +40,8 @@ def test_dimmer_turn_on(mock_openzwave): """Test turning on a dimmable Z-Wave light.""" node = MockNode() value = MockValue(data=0, node=node) - device = zwave.get_device(node=node, value=value, node_config={}) + values = MockLightValues(primary=value) + device = zwave.get_device(node=node, values=values, node_config={}) device.turn_on() @@ -51,7 +65,8 @@ def test_dimmer_value_changed(mock_openzwave): """Test value changed for dimmer lights.""" node = MockNode() value = MockValue(data=0, node=node) - device = zwave.get_device(node=node, value=value, node_config={}) + values = MockLightValues(primary=value) + device = zwave.get_device(node=node, values=values, node_config={}) assert not device.is_on diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py index 2096f1c67fa..2594e0acd4c 100644 --- a/tests/mock/zwave.py +++ b/tests/mock/zwave.py @@ -73,3 +73,20 @@ class MockValue(MagicMock): def _get_child_mock(self, **kw): """Create child mocks with right MagicMock class.""" return MagicMock(**kw) + + +class MockEntityValues(): + """Mock Z-Wave entity values.""" + + def __init__(self, **kwargs): + """Initialize the mock zwave values.""" + self.primary = None + self.wakeup = None + self.battery = None + self.power = None + for name in kwargs: + setattr(self, name, kwargs[name]) + + def __iter__(self): + """Allow iteration over all values.""" + return iter(self.__dict__.values())