diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index a5a460d129e29b..f7735d9aa3600d 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -53,6 +53,7 @@ CONF_DEVICE_CONFIG = 'device_config' CONF_DEVICE_CONFIG_GLOB = 'device_config_glob' CONF_DEVICE_CONFIG_DOMAIN = 'device_config_domain' +CONF_TILT_OPEN_POSITION = 'tilt_open_position' DATA_ZWAVE_CONFIG = 'zwave_config' @@ -153,7 +154,8 @@ vol.Optional(CONF_REFRESH_VALUE, default=DEFAULT_CONF_REFRESH_VALUE): cv.boolean, vol.Optional(CONF_REFRESH_DELAY, default=DEFAULT_CONF_REFRESH_DELAY): - cv.positive_int + cv.positive_int, + vol.Optional(CONF_TILT_OPEN_POSITION): cv.positive_int, }) SIGNAL_REFRESH_ENTITY_FORMAT = 'zwave_refresh_entity_{}' diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index 1ab643bde11add..ec61e33a901c5a 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -2,21 +2,37 @@ import logging from homeassistant.core import callback from homeassistant.components.cover import ( - DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION) + DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION, ATTR_TILT_POSITION) from homeassistant.components.cover import CoverDevice from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( ZWaveDeviceEntity, CONF_INVERT_OPENCLOSE_BUTTONS, CONF_INVERT_PERCENT, - workaround) + workaround, CONF_TILT_OPEN_POSITION) from .const import ( COMMAND_CLASS_SWITCH_MULTILEVEL, COMMAND_CLASS_SWITCH_BINARY, - COMMAND_CLASS_BARRIER_OPERATOR, DATA_NETWORK) + COMMAND_CLASS_BARRIER_OPERATOR, COMMAND_CLASS_MANUFACTURER_PROPRIETARY, + DATA_NETWORK) _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE +def _to_hex_str(id_in_bytes): + """Convert a two byte value to a hex string. + + Example: 0x1234 --> '0x1234' + """ + return '0x{:04x}'.format(id_in_bytes) + + +# For whatever reason node.manufacturer_id is of type string. So we need +# to convert the values. +FIBARO = _to_hex_str(workaround.FIBARO) +FIBARO_SHUTTERS = [_to_hex_str(workaround.FGR222_SHUTTER2), + _to_hex_str(workaround.FGRM222_SHUTTER2)] + + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Old method of setting up Z-Wave covers.""" @@ -40,6 +56,10 @@ def get_device(hass, values, node_config, **kwargs): if (values.primary.command_class == COMMAND_CLASS_SWITCH_MULTILEVEL and values.primary.index == 0): + if values.primary.node.manufacturer_id == FIBARO \ + and values.primary.node.product_type in FIBARO_SHUTTERS: + return FibaroFGRM222(hass, values, invert_buttons, invert_percent, + node_config.get(CONF_TILT_OPEN_POSITION)) return ZwaveRollershutter(hass, values, invert_buttons, invert_percent) if values.primary.command_class == COMMAND_CLASS_SWITCH_BINARY: return ZwaveGarageDoorSwitch(values) @@ -191,3 +211,109 @@ def close_cover(self, **kwargs): def open_cover(self, **kwargs): """Open the garage door.""" self.values.primary.data = "Opened" + + +class FibaroFGRM222(ZwaveRollershutter): + """Implementation of proprietary features for Fibaro FGR-222 / FGRM-222. + + This adds support for the tilt feature for the ventian blind mode. + To enable this you need to configure the devices to use the venetian blind + mode and to enable the proprietary command class: + * Set "3: Reports type to Blind position reports sent" + to value "the main controller using Fibar Command Class" + * Set "10: Roller Shutter operating modes" + to value "2 - Venetian Blind Mode, with positioning" + """ + + def __init__(self, hass, values, invert_buttons, invert_percent, + open_tilt_position: int): + """Initialize the FGRM-222.""" + self._value_blinds = None + self._value_tilt = None + self._has_tilt_mode = False # type: bool + self._open_tilt_position = 50 # type: int + if open_tilt_position is not None: + self._open_tilt_position = open_tilt_position + super().__init__(hass, values, invert_buttons, invert_percent) + + @property + def current_cover_tilt_position(self) -> int: + """Get the tilt of the blinds. + + Saturate values <5 and >94 so that it's easier to detect the end + positions in automations. + """ + if not self._has_tilt_mode: + return None + if self._value_tilt.data <= 5: + return 0 + if self._value_tilt.data >= 95: + return 100 + return self._value_tilt.data + + def set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + if not self._has_tilt_mode: + _LOGGER.error("Can't set cover tilt as device is not yet set up.") + else: + # Limit the range to [0-99], as this what that the ZWave command + # accepts. + tilt_position = max(0, min(99, kwargs.get(ATTR_TILT_POSITION))) + _LOGGER.debug("setting tilt to %d", tilt_position) + self._value_tilt.data = tilt_position + + def open_cover_tilt(self, **kwargs): + """Set slats to horizontal position.""" + self.set_cover_tilt_position(tilt_position=self._open_tilt_position) + + def close_cover_tilt(self, **kwargs): + """Close the slats.""" + self.set_cover_tilt_position(tilt_position=0) + + def set_cover_position(self, **kwargs): + """Move the roller shutter to a specific position. + + If the venetian blinds mode is not activated, fall back to + the behavior of the parent class. + """ + if not self._has_tilt_mode: + super().set_cover_position(**kwargs) + else: + _LOGGER.debug('Setting cover position to %s', + kwargs.get(ATTR_POSITION)) + self._value_blinds.data = kwargs.get(ATTR_POSITION) + + def _configure_values(self): + """Get the value objects from the node.""" + for value in self.node.get_values( + class_id=COMMAND_CLASS_MANUFACTURER_PROPRIETARY).values(): + if value is None: + continue + if value.index == 0: + self._value_blinds = value + elif value.index == 1: + self._value_tilt = value + else: + _LOGGER.warning('Undefined index %d for this command class', + value.index) + + # If we've ever seen tilt != 0 assume we are in the venetian mode. + # This is not the best way to detect it. Once the bug below is + # resolved, check the config values of the node and only enable the + # tilt mode if they are set accordingly. + # https://github.com/home-assistant/home-assistant/issues/24404 + + if self._value_tilt is not None and self._value_tilt.data > 0: + self._has_tilt_mode = True + _LOGGER.info('Zwave node %s is a Fibaro FGR-222/FGRM-222' + ' with tilt support.', + self.node_id) + + def update_properties(self): + """React on properties being updated.""" + if not self._has_tilt_mode: + self._configure_values() + if self._value_blinds is not None: + self._current_position = self._value_blinds.data + else: + super().update_properties()