From 5c516ad01303f7ba5a1ed8db631ab361463228d1 Mon Sep 17 00:00:00 2001 From: shbatm Date: Thu, 28 May 2020 08:51:56 -0500 Subject: [PATCH] Add support for Insteon 2444-222 to ISY994 (#36212) * Add support for Insteon 2444-222 Micro Open/Close Module * Avoid breaking changes on cover * Update homeassistant/components/isy994/cover.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/isy994/const.py | 45 +++++++++++++----------- homeassistant/components/isy994/cover.py | 36 +++++++++++++++++-- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index f7042a5860a..cf8b304179d 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -168,6 +168,23 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" UDN_UUID_PREFIX = "uuid:" ISY_URL_POSTFIX = "/desc" +# Special Units of Measure +UOM_ISYV4_DEGREES = "degrees" +UOM_ISYV4_NONE = "n/a" + +UOM_ISY_CELSIUS = 1 +UOM_ISY_FAHRENHEIT = 2 + +UOM_8_BIT_RANGE = "100" +UOM_BARRIER = "97" +UOM_DOUBLE_TEMP = "101" +UOM_HVAC_ACTIONS = "66" +UOM_HVAC_MODE_GENERIC = "67" +UOM_HVAC_MODE_INSTEON = "98" +UOM_FAN_MODES = "99" +UOM_INDEX = "25" +UOM_ON_OFF = "2" + # Do not use the Home Assistant consts for the states here - we're matching exact API # responses, not using them for Home Assistant states # Insteon Types: https://www.universal-devices.com/developers/wsdk/5.0.4/1_fam.xml @@ -232,10 +249,10 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: [], }, COVER: { - FILTER_UOM: ["97"], + FILTER_UOM: [UOM_BARRIER], FILTER_STATES: ["open", "closed", "closing", "opening", "stopped"], - FILTER_NODE_DEF_ID: [], - FILTER_INSTEON_TYPE: [], + FILTER_NODE_DEF_ID: ["DimmerMotorSwitch_ADV"], + FILTER_INSTEON_TYPE: [TYPE_CATEGORY_COVER], FILTER_ZWAVE_CAT: [], }, LIGHT: { @@ -256,7 +273,7 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: ["109", "119"], }, SWITCH: { - FILTER_UOM: ["2", "78"], + FILTER_UOM: [UOM_ON_OFF, "78"], FILTER_STATES: ["on", "off"], FILTER_NODE_DEF_ID: [ "AlertModuleArmed", @@ -286,7 +303,7 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: ["121", "122", "123", "137", "141", "147"], }, CLIMATE: { - FILTER_UOM: ["2"], + FILTER_UOM: [UOM_ON_OFF], FILTER_STATES: ["heating", "cooling", "idle", "fan_only", "off"], FILTER_NODE_DEF_ID: ["TempLinc", "Thermostat"], FILTER_INSTEON_TYPE: ["4.8", TYPE_CATEGORY_CLIMATE], @@ -294,20 +311,6 @@ NODE_FILTERS = { }, } -UOM_ISYV4_DEGREES = "degrees" -UOM_ISYV4_NONE = "n/a" - -UOM_ISY_CELSIUS = 1 -UOM_ISY_FAHRENHEIT = 2 - -UOM_DOUBLE_TEMP = "101" -UOM_HVAC_ACTIONS = "66" -UOM_HVAC_MODE_GENERIC = "67" -UOM_HVAC_MODE_INSTEON = "98" -UOM_FAN_MODES = "99" -UOM_INDEX = "25" -UOM_ON_OFF = "2" - UOM_FRIENDLY_NAME = { "1": "A", "3": f"btu/{TIME_HOURS}", @@ -388,7 +391,7 @@ UOM_FRIENDLY_NAME = { "90": FREQUENCY_HERTZ, "91": DEGREE, "92": f"{DEGREE} South", - "100": "", # Range 0-255, no unit. + UOM_8_BIT_RANGE: "", # Range 0-255, no unit. UOM_DOUBLE_TEMP: UOM_DOUBLE_TEMP, "102": "kWs", "103": "$", @@ -556,7 +559,7 @@ UOM_TO_STATES = { 3: "moderately polluted", 4: "highly polluted", }, - "97": { # Barrier Status + UOM_BARRIER: { # Barrier Status **{ 0: STATE_CLOSED, 100: STATE_OPEN, diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index e0e47592a37..bbcc6f3bf15 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -3,11 +3,25 @@ from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN -from homeassistant.components.cover import DOMAIN as COVER, CoverEntity +from homeassistant.components.cover import ( + ATTR_POSITION, + DOMAIN as COVER, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, + CoverEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from .const import _LOGGER, DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_PROGRAMS +from .const import ( + _LOGGER, + DOMAIN as ISY994_DOMAIN, + ISY994_NODES, + ISY994_PROGRAMS, + UOM_8_BIT_RANGE, + UOM_BARRIER, +) from .entity import ISYNodeEntity, ISYProgramEntity from .helpers import migrate_old_unique_ids from .services import async_setup_device_services @@ -40,6 +54,8 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): """Return the current cover position.""" if self._node.status == ISY_VALUE_UNKNOWN: return None + if self._node.uom == UOM_8_BIT_RANGE: + return int(self._node.status * 100 / 255) return sorted((0, self._node.status, 100))[1] @property @@ -49,9 +65,15 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): return None return self._node.status == 0 + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + def open_cover(self, **kwargs) -> None: """Send the open cover command to the ISY994 cover device.""" - if not self._node.turn_on(val=100): + val = 100 if self._node.uom == UOM_BARRIER else None + if not self._node.turn_on(val=val): _LOGGER.error("Unable to open the cover") def close_cover(self, **kwargs) -> None: @@ -59,6 +81,14 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): if not self._node.turn_off(): _LOGGER.error("Unable to close the cover") + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + position = kwargs.get(ATTR_POSITION) + if position and self._node.uom == UOM_8_BIT_RANGE: + position = int(position * 255 / 100) + if not self._node.turn_on(val=position): + _LOGGER.error("Unable to set cover position") + class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity): """Representation of an ISY994 cover program."""