mirror of https://github.com/home-assistant/core
ISY994 migration to PyISY v2 (Structure Changes to enable upgrade, Part 1) (#35212)
* ISY994 Structure updates in prep for PyISYv2 (Part 1) - Correct node categorization. - Move constants to separate file. - Consolidate Logging to Constants file. - Use Home Assistant Constants where possible. - Use string literals where possible. - Rename "domain" to "platform" in most places. - Add additional device support (NODE_FILTER updates). * Update categorize_programs per review * add @shbatm as codeowner for ISY994 integration
This commit is contained in:
parent
dd715fcc3a
commit
4be1006b71
|
@ -196,7 +196,7 @@ homeassistant/components/ipp/* @ctalkington
|
|||
homeassistant/components/iqvia/* @bachya
|
||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||
homeassistant/components/islamic_prayer_times/* @engrbm87
|
||||
homeassistant/components/isy994/* @bdraco
|
||||
homeassistant/components/isy994/* @bdraco @shbatm
|
||||
homeassistant/components/izone/* @Swamp-Ig
|
||||
homeassistant/components/jewish_calendar/* @tsvi
|
||||
homeassistant/components/juicenet/* @jesserockz
|
||||
|
|
|
@ -1,40 +1,47 @@
|
|||
"""Support the ISY-994 controllers."""
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import PyISY
|
||||
from PyISY.Nodes import Group
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
|
||||
from homeassistant.components.fan import DOMAIN as FAN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType, Dict
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "isy994"
|
||||
|
||||
CONF_IGNORE_STRING = "ignore_string"
|
||||
CONF_SENSOR_STRING = "sensor_string"
|
||||
CONF_ENABLE_CLIMATE = "enable_climate"
|
||||
CONF_TLS_VER = "tls"
|
||||
|
||||
DEFAULT_IGNORE_STRING = "{IGNORE ME}"
|
||||
DEFAULT_SENSOR_STRING = "sensor"
|
||||
|
||||
KEY_ACTIONS = "actions"
|
||||
KEY_FOLDER = "folder"
|
||||
KEY_MY_PROGRAMS = "My Programs"
|
||||
KEY_STATUS = "status"
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
CONF_ENABLE_CLIMATE,
|
||||
CONF_IGNORE_STRING,
|
||||
CONF_SENSOR_STRING,
|
||||
CONF_TLS_VER,
|
||||
DEFAULT_IGNORE_STRING,
|
||||
DEFAULT_SENSOR_STRING,
|
||||
DOMAIN,
|
||||
ISY994_NODES,
|
||||
ISY994_PROGRAMS,
|
||||
ISY994_WEATHER,
|
||||
ISY_GROUP_PLATFORM,
|
||||
KEY_ACTIONS,
|
||||
KEY_FOLDER,
|
||||
KEY_MY_PROGRAMS,
|
||||
KEY_STATUS,
|
||||
NODE_FILTERS,
|
||||
SUPPORTED_PLATFORMS,
|
||||
SUPPORTED_PROGRAM_PLATFORMS,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -57,123 +64,11 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
# Do not use the Home Assistant consts for the states here - we're matching
|
||||
# exact API responses, not using them for Home Assistant states
|
||||
NODE_FILTERS = {
|
||||
"binary_sensor": {
|
||||
"uom": [],
|
||||
"states": [],
|
||||
"node_def_id": ["BinaryAlarm", "BinaryAlarm_ADV"],
|
||||
"insteon_type": ["16."], # Does a startswith() match; include the dot
|
||||
},
|
||||
"sensor": {
|
||||
# This is just a more-readable way of including MOST uoms between 1-100
|
||||
# (Remember that range() is non-inclusive of the stop value)
|
||||
"uom": (
|
||||
["1"]
|
||||
+ list(map(str, range(3, 11)))
|
||||
+ list(map(str, range(12, 51)))
|
||||
+ list(map(str, range(52, 66)))
|
||||
+ list(map(str, range(69, 78)))
|
||||
+ ["79"]
|
||||
+ list(map(str, range(82, 97)))
|
||||
),
|
||||
"states": [],
|
||||
"node_def_id": ["IMETER_SOLO"],
|
||||
"insteon_type": ["9.0.", "9.7."],
|
||||
},
|
||||
"lock": {
|
||||
"uom": ["11"],
|
||||
"states": ["locked", "unlocked"],
|
||||
"node_def_id": ["DoorLock"],
|
||||
"insteon_type": ["15."],
|
||||
},
|
||||
"fan": {
|
||||
"uom": [],
|
||||
"states": ["off", "low", "med", "high"],
|
||||
"node_def_id": ["FanLincMotor"],
|
||||
"insteon_type": ["1.46."],
|
||||
},
|
||||
"cover": {
|
||||
"uom": ["97"],
|
||||
"states": ["open", "closed", "closing", "opening", "stopped"],
|
||||
"node_def_id": [],
|
||||
"insteon_type": [],
|
||||
},
|
||||
"light": {
|
||||
"uom": ["51"],
|
||||
"states": ["on", "off", UNIT_PERCENTAGE],
|
||||
"node_def_id": [
|
||||
"DimmerLampSwitch",
|
||||
"DimmerLampSwitch_ADV",
|
||||
"DimmerSwitchOnly",
|
||||
"DimmerSwitchOnly_ADV",
|
||||
"DimmerLampOnly",
|
||||
"BallastRelayLampSwitch",
|
||||
"BallastRelayLampSwitch_ADV",
|
||||
"RemoteLinc2",
|
||||
"RemoteLinc2_ADV",
|
||||
"KeypadDimmer",
|
||||
"KeypadDimmer_ADV",
|
||||
],
|
||||
"insteon_type": ["1."],
|
||||
},
|
||||
"switch": {
|
||||
"uom": ["2", "78"],
|
||||
"states": ["on", "off"],
|
||||
"node_def_id": [
|
||||
"OnOffControl",
|
||||
"RelayLampSwitch",
|
||||
"RelayLampSwitch_ADV",
|
||||
"RelaySwitchOnlyPlusQuery",
|
||||
"RelaySwitchOnlyPlusQuery_ADV",
|
||||
"RelayLampOnly",
|
||||
"RelayLampOnly_ADV",
|
||||
"KeypadButton",
|
||||
"KeypadButton_ADV",
|
||||
"EZRAIN_Input",
|
||||
"EZRAIN_Output",
|
||||
"EZIO2x4_Input",
|
||||
"EZIO2x4_Input_ADV",
|
||||
"BinaryControl",
|
||||
"BinaryControl_ADV",
|
||||
"AlertModuleSiren",
|
||||
"AlertModuleSiren_ADV",
|
||||
"AlertModuleArmed",
|
||||
"Siren",
|
||||
"Siren_ADV",
|
||||
"X10",
|
||||
"KeypadRelay",
|
||||
"KeypadRelay_ADV",
|
||||
],
|
||||
"insteon_type": ["2.", "9.10.", "9.11.", "113."],
|
||||
},
|
||||
}
|
||||
|
||||
SUPPORTED_DOMAINS = [
|
||||
"binary_sensor",
|
||||
"sensor",
|
||||
"lock",
|
||||
"fan",
|
||||
"cover",
|
||||
"light",
|
||||
"switch",
|
||||
]
|
||||
SUPPORTED_PROGRAM_DOMAINS = ["binary_sensor", "lock", "fan", "cover", "switch"]
|
||||
|
||||
# ISY Scenes are more like Switches than Home Assistant Scenes
|
||||
# (they can turn off, and report their state)
|
||||
SCENE_DOMAIN = "switch"
|
||||
|
||||
ISY994_NODES = "isy994_nodes"
|
||||
ISY994_WEATHER = "isy994_weather"
|
||||
ISY994_PROGRAMS = "isy994_programs"
|
||||
|
||||
WeatherNode = namedtuple("WeatherNode", ("status", "name", "uom"))
|
||||
|
||||
|
||||
def _check_for_node_def(hass: HomeAssistant, node, single_domain: str = None) -> bool:
|
||||
"""Check if the node matches the node_def_id for any domains.
|
||||
def _check_for_node_def(hass: HomeAssistant, node, single_platform: str = None) -> bool:
|
||||
"""Check if the node matches the node_def_id for any platforms.
|
||||
|
||||
This is only present on the 5.0 ISY firmware, and is the most reliable
|
||||
way to determine a device's type.
|
||||
|
@ -184,10 +79,10 @@ def _check_for_node_def(hass: HomeAssistant, node, single_domain: str = None) ->
|
|||
|
||||
node_def_id = node.node_def_id
|
||||
|
||||
domains = SUPPORTED_DOMAINS if not single_domain else [single_domain]
|
||||
for domain in domains:
|
||||
if node_def_id in NODE_FILTERS[domain]["node_def_id"]:
|
||||
hass.data[ISY994_NODES][domain].append(node)
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_def_id in NODE_FILTERS[platform]["node_def_id"]:
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
_LOGGER.warning("Unsupported node: %s, type: %s", node.name, node.type)
|
||||
|
@ -195,9 +90,9 @@ def _check_for_node_def(hass: HomeAssistant, node, single_domain: str = None) ->
|
|||
|
||||
|
||||
def _check_for_insteon_type(
|
||||
hass: HomeAssistant, node, single_domain: str = None
|
||||
hass: HomeAssistant, node, single_platform: str = None
|
||||
) -> bool:
|
||||
"""Check if the node matches the Insteon type for any domains.
|
||||
"""Check if the node matches the Insteon type for any platforms.
|
||||
|
||||
This is for (presumably) every version of the ISY firmware, but only
|
||||
works for Insteon device. "Node Server" (v5+) and Z-Wave and others will
|
||||
|
@ -208,32 +103,32 @@ def _check_for_insteon_type(
|
|||
return False
|
||||
|
||||
device_type = node.type
|
||||
domains = SUPPORTED_DOMAINS if not single_domain else [single_domain]
|
||||
for domain in domains:
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if any(
|
||||
[
|
||||
device_type.startswith(t)
|
||||
for t in set(NODE_FILTERS[domain]["insteon_type"])
|
||||
for t in set(NODE_FILTERS[platform]["insteon_type"])
|
||||
]
|
||||
):
|
||||
|
||||
# Hacky special-case just for FanLinc, which has a light module
|
||||
# as one of its nodes. Note that this special-case is not necessary
|
||||
# on ISY 5.x firmware as it uses the superior NodeDefs method
|
||||
if domain == "fan" and int(node.nid[-1]) == 1:
|
||||
hass.data[ISY994_NODES]["light"].append(node)
|
||||
if platform == FAN and int(node.nid[-1]) == 1:
|
||||
hass.data[ISY994_NODES][LIGHT].append(node)
|
||||
return True
|
||||
|
||||
hass.data[ISY994_NODES][domain].append(node)
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _check_for_uom_id(
|
||||
hass: HomeAssistant, node, single_domain: str = None, uom_list: list = None
|
||||
hass: HomeAssistant, node, single_platform: str = None, uom_list: list = None
|
||||
) -> bool:
|
||||
"""Check if a node's uom matches any of the domains uom filter.
|
||||
"""Check if a node's uom matches any of the platforms uom filter.
|
||||
|
||||
This is used for versions of the ISY firmware that report uoms as a single
|
||||
ID. We can often infer what type of device it is by that ID.
|
||||
|
@ -246,20 +141,20 @@ def _check_for_uom_id(
|
|||
|
||||
if uom_list:
|
||||
if node_uom.intersection(uom_list):
|
||||
hass.data[ISY994_NODES][single_domain].append(node)
|
||||
hass.data[ISY994_NODES][single_platform].append(node)
|
||||
return True
|
||||
else:
|
||||
domains = SUPPORTED_DOMAINS if not single_domain else [single_domain]
|
||||
for domain in domains:
|
||||
if node_uom.intersection(NODE_FILTERS[domain]["uom"]):
|
||||
hass.data[ISY994_NODES][domain].append(node)
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_uom.intersection(NODE_FILTERS[platform]["uom"]):
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _check_for_states_in_uom(
|
||||
hass: HomeAssistant, node, single_domain: str = None, states_list: list = None
|
||||
hass: HomeAssistant, node, single_platform: str = None, states_list: list = None
|
||||
) -> bool:
|
||||
"""Check if a list of uoms matches two possible filters.
|
||||
|
||||
|
@ -275,13 +170,13 @@ def _check_for_states_in_uom(
|
|||
|
||||
if states_list:
|
||||
if node_uom == set(states_list):
|
||||
hass.data[ISY994_NODES][single_domain].append(node)
|
||||
hass.data[ISY994_NODES][single_platform].append(node)
|
||||
return True
|
||||
else:
|
||||
domains = SUPPORTED_DOMAINS if not single_domain else [single_domain]
|
||||
for domain in domains:
|
||||
if node_uom == set(NODE_FILTERS[domain]["states"]):
|
||||
hass.data[ISY994_NODES][domain].append(node)
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_uom == set(NODE_FILTERS[platform]["states"]):
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
@ -289,9 +184,9 @@ def _check_for_states_in_uom(
|
|||
|
||||
def _is_sensor_a_binary_sensor(hass: HomeAssistant, node) -> bool:
|
||||
"""Determine if the given sensor node should be a binary_sensor."""
|
||||
if _check_for_node_def(hass, node, single_domain="binary_sensor"):
|
||||
if _check_for_node_def(hass, node, single_platform=BINARY_SENSOR):
|
||||
return True
|
||||
if _check_for_insteon_type(hass, node, single_domain="binary_sensor"):
|
||||
if _check_for_insteon_type(hass, node, single_platform=BINARY_SENSOR):
|
||||
return True
|
||||
|
||||
# For the next two checks, we're providing our own set of uoms that
|
||||
|
@ -299,11 +194,11 @@ def _is_sensor_a_binary_sensor(hass: HomeAssistant, node) -> bool:
|
|||
# checks in the context of already knowing that this is definitely a
|
||||
# sensor device.
|
||||
if _check_for_uom_id(
|
||||
hass, node, single_domain="binary_sensor", uom_list=["2", "78"]
|
||||
hass, node, single_platform=BINARY_SENSOR, uom_list=["2", "78"]
|
||||
):
|
||||
return True
|
||||
if _check_for_states_in_uom(
|
||||
hass, node, single_domain="binary_sensor", states_list=["on", "off"]
|
||||
hass, node, single_platform=BINARY_SENSOR, states_list=["on", "off"]
|
||||
):
|
||||
return True
|
||||
|
||||
|
@ -313,7 +208,7 @@ def _is_sensor_a_binary_sensor(hass: HomeAssistant, node) -> bool:
|
|||
def _categorize_nodes(
|
||||
hass: HomeAssistant, nodes, ignore_identifier: str, sensor_identifier: str
|
||||
) -> None:
|
||||
"""Sort the nodes to their proper domains."""
|
||||
"""Sort the nodes to their proper platforms."""
|
||||
for (path, node) in nodes:
|
||||
ignored = ignore_identifier in path or ignore_identifier in node.name
|
||||
if ignored:
|
||||
|
@ -321,7 +216,7 @@ def _categorize_nodes(
|
|||
continue
|
||||
|
||||
if isinstance(node, Group):
|
||||
hass.data[ISY994_NODES][SCENE_DOMAIN].append(node)
|
||||
hass.data[ISY994_NODES][ISY_GROUP_PLATFORM].append(node)
|
||||
continue
|
||||
|
||||
if sensor_identifier in path or sensor_identifier in node.name:
|
||||
|
@ -330,7 +225,7 @@ def _categorize_nodes(
|
|||
if _is_sensor_a_binary_sensor(hass, node):
|
||||
continue
|
||||
|
||||
hass.data[ISY994_NODES]["sensor"].append(node)
|
||||
hass.data[ISY994_NODES][SENSOR].append(node)
|
||||
continue
|
||||
|
||||
# We have a bunch of different methods for determining the device type,
|
||||
|
@ -348,9 +243,9 @@ def _categorize_nodes(
|
|||
|
||||
def _categorize_programs(hass: HomeAssistant, programs: dict) -> None:
|
||||
"""Categorize the ISY994 programs."""
|
||||
for domain in SUPPORTED_PROGRAM_DOMAINS:
|
||||
for platform in SUPPORTED_PROGRAM_PLATFORMS:
|
||||
try:
|
||||
folder = programs[KEY_MY_PROGRAMS][f"HA.{domain}"]
|
||||
folder = programs[KEY_MY_PROGRAMS][f"HA.{platform}"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
|
@ -361,7 +256,7 @@ def _categorize_programs(hass: HomeAssistant, programs: dict) -> None:
|
|||
try:
|
||||
status = entity_folder[KEY_STATUS]
|
||||
assert status.dtype == "program", "Not a program"
|
||||
if domain != "binary_sensor":
|
||||
if platform != BINARY_SENSOR:
|
||||
actions = entity_folder[KEY_ACTIONS]
|
||||
assert actions.dtype == "program", "Not a program"
|
||||
else:
|
||||
|
@ -375,7 +270,7 @@ def _categorize_programs(hass: HomeAssistant, programs: dict) -> None:
|
|||
continue
|
||||
|
||||
entity = (entity_folder.name, status, actions)
|
||||
hass.data[ISY994_PROGRAMS][domain].append(entity)
|
||||
hass.data[ISY994_PROGRAMS][platform].append(entity)
|
||||
|
||||
|
||||
def _categorize_weather(hass: HomeAssistant, climate) -> None:
|
||||
|
@ -396,14 +291,14 @@ def _categorize_weather(hass: HomeAssistant, climate) -> None:
|
|||
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the ISY 994 platform."""
|
||||
hass.data[ISY994_NODES] = {}
|
||||
for domain in SUPPORTED_DOMAINS:
|
||||
hass.data[ISY994_NODES][domain] = []
|
||||
for platform in SUPPORTED_PLATFORMS:
|
||||
hass.data[ISY994_NODES][platform] = []
|
||||
|
||||
hass.data[ISY994_WEATHER] = []
|
||||
|
||||
hass.data[ISY994_PROGRAMS] = {}
|
||||
for domain in SUPPORTED_DOMAINS:
|
||||
hass.data[ISY994_PROGRAMS][domain] = []
|
||||
for platform in SUPPORTED_PROGRAM_PLATFORMS:
|
||||
hass.data[ISY994_PROGRAMS][platform] = []
|
||||
|
||||
isy_config = config.get(DOMAIN)
|
||||
|
||||
|
@ -452,8 +347,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
|
||||
|
||||
# Load platforms for the devices in the ISY controller that we support.
|
||||
for component in SUPPORTED_DOMAINS:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
for platform in SUPPORTED_PLATFORMS:
|
||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||
|
||||
isy.auto_update = True
|
||||
return True
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""Support for ISY994 binary sensors."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
|
@ -11,14 +13,7 @@ from homeassistant.helpers.typing import ConfigType
|
|||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ISY_DEVICE_TYPES = {
|
||||
"moisture": ["16.8", "16.13", "16.14"],
|
||||
"opening": ["16.9", "16.6", "16.7", "16.2", "16.17", "16.20", "16.21"],
|
||||
"motion": ["16.1", "16.4", "16.5", "16.3"],
|
||||
}
|
||||
from .const import _LOGGER, ISY_BIN_SENS_DEVICE_TYPES
|
||||
|
||||
|
||||
def setup_platform(
|
||||
|
@ -29,7 +24,7 @@ def setup_platform(
|
|||
devices_by_nid = {}
|
||||
child_nodes = []
|
||||
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][BINARY_SENSOR]:
|
||||
if node.parent_node is None:
|
||||
device = ISYBinarySensorEntity(node)
|
||||
devices.append(device)
|
||||
|
@ -69,7 +64,7 @@ def setup_platform(
|
|||
device = ISYBinarySensorEntity(node)
|
||||
devices.append(device)
|
||||
|
||||
for name, status, _ in hass.data[ISY994_PROGRAMS][DOMAIN]:
|
||||
for name, status, _ in hass.data[ISY994_PROGRAMS][BINARY_SENSOR]:
|
||||
devices.append(ISYBinarySensorProgram(name, status))
|
||||
|
||||
add_entities(devices)
|
||||
|
@ -83,7 +78,7 @@ def _detect_device_type(node) -> str:
|
|||
return None
|
||||
|
||||
split_type = device_type.split(".")
|
||||
for device_class, ids in ISY_DEVICE_TYPES.items():
|
||||
for device_class, ids in ISY_BIN_SENS_DEVICE_TYPES.items():
|
||||
if f"{split_type[0]}.{split_type[1]}" in ids:
|
||||
return device_class
|
||||
|
||||
|
|
|
@ -0,0 +1,479 @@
|
|||
"""Constants for the ISY994 Platform."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_COOL,
|
||||
CURRENT_HVAC_FAN,
|
||||
CURRENT_HVAC_HEAT,
|
||||
CURRENT_HVAC_IDLE,
|
||||
FAN_AUTO,
|
||||
FAN_HIGH,
|
||||
FAN_MEDIUM,
|
||||
FAN_ON,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_DRY,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_AWAY,
|
||||
PRESET_BOOST,
|
||||
)
|
||||
from homeassistant.components.cover import DOMAIN as COVER
|
||||
from homeassistant.components.fan import DOMAIN as FAN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT
|
||||
from homeassistant.components.lock import DOMAIN as LOCK
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
FREQUENCY_HERTZ,
|
||||
LENGTH_CENTIMETERS,
|
||||
LENGTH_FEET,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_METERS,
|
||||
MASS_KILOGRAMS,
|
||||
MASS_POUNDS,
|
||||
POWER_WATT,
|
||||
PRESSURE_INHG,
|
||||
SERVICE_LOCK,
|
||||
SERVICE_UNLOCK,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
STATE_CLOSED,
|
||||
STATE_CLOSING,
|
||||
STATE_LOCKED,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
STATE_PROBLEM,
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNLOCKED,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
TEMP_KELVIN,
|
||||
TIME_DAYS,
|
||||
TIME_HOURS,
|
||||
TIME_MILLISECONDS,
|
||||
TIME_MINUTES,
|
||||
TIME_MONTHS,
|
||||
TIME_SECONDS,
|
||||
TIME_YEARS,
|
||||
UNIT_PERCENTAGE,
|
||||
UV_INDEX,
|
||||
VOLT,
|
||||
VOLUME_GALLONS,
|
||||
VOLUME_LITERS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__package__)
|
||||
|
||||
DOMAIN = "isy994"
|
||||
|
||||
MANUFACTURER = "Universal Devices, Inc"
|
||||
|
||||
CONF_IGNORE_STRING = "ignore_string"
|
||||
CONF_SENSOR_STRING = "sensor_string"
|
||||
CONF_ENABLE_CLIMATE = "enable_climate"
|
||||
CONF_TLS_VER = "tls"
|
||||
|
||||
DEFAULT_IGNORE_STRING = "{IGNORE ME}"
|
||||
DEFAULT_SENSOR_STRING = "sensor"
|
||||
DEFAULT_TLS_VERSION = 1.1
|
||||
|
||||
KEY_ACTIONS = "actions"
|
||||
KEY_FOLDER = "folder"
|
||||
KEY_MY_PROGRAMS = "My Programs"
|
||||
KEY_STATUS = "status"
|
||||
|
||||
SUPPORTED_PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH]
|
||||
SUPPORTED_PROGRAM_PLATFORMS = [BINARY_SENSOR, LOCK, FAN, COVER, SWITCH]
|
||||
|
||||
SUPPORTED_BIN_SENS_CLASSES = ["moisture", "opening", "motion", "climate"]
|
||||
|
||||
# ISY Scenes are more like Switches than Home Assistant Scenes
|
||||
# (they can turn off, and report their state)
|
||||
ISY_GROUP_PLATFORM = SWITCH
|
||||
|
||||
ISY994_ISY = "isy"
|
||||
ISY994_NODES = "isy994_nodes"
|
||||
ISY994_WEATHER = "isy994_weather"
|
||||
ISY994_PROGRAMS = "isy994_programs"
|
||||
|
||||
# Do not use the Home Assistant consts for the states here - we're matching exact API
|
||||
# responses, not using them for Home Assistant states
|
||||
NODE_FILTERS = {
|
||||
BINARY_SENSOR: {
|
||||
"uom": [],
|
||||
"states": [],
|
||||
"node_def_id": [
|
||||
"BinaryAlarm",
|
||||
"BinaryAlarm_ADV",
|
||||
"BinaryControl",
|
||||
"BinaryControl_ADV",
|
||||
"EZIO2x4_Input",
|
||||
"EZRAIN_Input",
|
||||
"OnOffControl",
|
||||
"OnOffControl_ADV",
|
||||
],
|
||||
"insteon_type": [
|
||||
"7.0.",
|
||||
"7.13.",
|
||||
"16.",
|
||||
], # Does a startswith() match; include the dot
|
||||
},
|
||||
SENSOR: {
|
||||
# This is just a more-readable way of including MOST uoms between 1-100
|
||||
# (Remember that range() is non-inclusive of the stop value)
|
||||
"uom": (
|
||||
["1"]
|
||||
+ list(map(str, range(3, 11)))
|
||||
+ list(map(str, range(12, 51)))
|
||||
+ list(map(str, range(52, 66)))
|
||||
+ list(map(str, range(69, 78)))
|
||||
+ ["79"]
|
||||
+ list(map(str, range(82, 97)))
|
||||
),
|
||||
"states": [],
|
||||
"node_def_id": ["IMETER_SOLO", "EZIO2x4_Input_ADV"],
|
||||
"insteon_type": ["9.0.", "9.7."],
|
||||
},
|
||||
LOCK: {
|
||||
"uom": ["11"],
|
||||
"states": ["locked", "unlocked"],
|
||||
"node_def_id": ["DoorLock"],
|
||||
"insteon_type": ["15.", "4.64."],
|
||||
},
|
||||
FAN: {
|
||||
"uom": [],
|
||||
"states": ["off", "low", "med", "high"],
|
||||
"node_def_id": ["FanLincMotor"],
|
||||
"insteon_type": ["1.46."],
|
||||
},
|
||||
COVER: {
|
||||
"uom": ["97"],
|
||||
"states": ["open", "closed", "closing", "opening", "stopped"],
|
||||
"node_def_id": [],
|
||||
"insteon_type": [],
|
||||
},
|
||||
LIGHT: {
|
||||
"uom": ["51"],
|
||||
"states": ["on", "off", "%"],
|
||||
"node_def_id": [
|
||||
"BallastRelayLampSwitch",
|
||||
"BallastRelayLampSwitch_ADV",
|
||||
"DimmerLampOnly",
|
||||
"DimmerLampSwitch",
|
||||
"DimmerLampSwitch_ADV",
|
||||
"DimmerSwitchOnly",
|
||||
"DimmerSwitchOnly_ADV",
|
||||
"KeypadDimmer",
|
||||
"KeypadDimmer_ADV",
|
||||
],
|
||||
"insteon_type": ["1."],
|
||||
},
|
||||
SWITCH: {
|
||||
"uom": ["2", "78"],
|
||||
"states": ["on", "off"],
|
||||
"node_def_id": [
|
||||
"AlertModuleArmed",
|
||||
"AlertModuleSiren",
|
||||
"AlertModuleSiren_ADV",
|
||||
"EZIO2x4_Output",
|
||||
"EZRAIN_Output",
|
||||
"KeypadButton",
|
||||
"KeypadButton_ADV",
|
||||
"KeypadRelay",
|
||||
"KeypadRelay_ADV",
|
||||
"RelayLampOnly",
|
||||
"RelayLampOnly_ADV",
|
||||
"RelayLampSwitch",
|
||||
"RelayLampSwitch_ADV",
|
||||
"RelaySwitchOnlyPlusQuery",
|
||||
"RelaySwitchOnlyPlusQuery_ADV",
|
||||
"RemoteLinc2",
|
||||
"RemoteLinc2_ADV",
|
||||
"Siren",
|
||||
"Siren_ADV",
|
||||
"X10",
|
||||
],
|
||||
"insteon_type": ["0.16.", "2.", "7.3.255.", "9.10.", "9.11.", "113."],
|
||||
},
|
||||
}
|
||||
|
||||
UOM_FRIENDLY_NAME = {
|
||||
"1": "A",
|
||||
"3": f"btu/{TIME_HOURS}",
|
||||
"4": TEMP_CELSIUS,
|
||||
"5": LENGTH_CENTIMETERS,
|
||||
"6": "ft³",
|
||||
"7": f"ft³/{TIME_MINUTES}",
|
||||
"8": "m³",
|
||||
"9": TIME_DAYS,
|
||||
"10": TIME_DAYS,
|
||||
"12": "dB",
|
||||
"13": "dB A",
|
||||
"14": DEGREE,
|
||||
"16": "macroseismic",
|
||||
"17": TEMP_FAHRENHEIT,
|
||||
"18": LENGTH_FEET,
|
||||
"19": TIME_HOURS,
|
||||
"20": TIME_HOURS,
|
||||
"21": "%AH",
|
||||
"22": "%RH",
|
||||
"23": PRESSURE_INHG,
|
||||
"24": f"{LENGTH_INCHES}/{TIME_HOURS}",
|
||||
"25": "index",
|
||||
"26": TEMP_KELVIN,
|
||||
"27": "keyword",
|
||||
"28": MASS_KILOGRAMS,
|
||||
"29": "kV",
|
||||
"30": "kW",
|
||||
"31": "kPa",
|
||||
"32": SPEED_KILOMETERS_PER_HOUR,
|
||||
"33": ENERGY_KILO_WATT_HOUR,
|
||||
"34": "liedu",
|
||||
"35": VOLUME_LITERS,
|
||||
"36": "lx",
|
||||
"37": "mercalli",
|
||||
"38": LENGTH_METERS,
|
||||
"39": f"{LENGTH_METERS}³/{TIME_HOURS}",
|
||||
"40": SPEED_METERS_PER_SECOND,
|
||||
"41": "mA",
|
||||
"42": TIME_MILLISECONDS,
|
||||
"43": "mV",
|
||||
"44": TIME_MINUTES,
|
||||
"45": TIME_MINUTES,
|
||||
"46": f"mm/{TIME_HOURS}",
|
||||
"47": TIME_MONTHS,
|
||||
"48": SPEED_MILES_PER_HOUR,
|
||||
"49": SPEED_METERS_PER_SECOND,
|
||||
"50": "Ω",
|
||||
"51": UNIT_PERCENTAGE,
|
||||
"52": MASS_POUNDS,
|
||||
"53": "pf",
|
||||
"54": CONCENTRATION_PARTS_PER_MILLION,
|
||||
"55": "pulse count",
|
||||
"57": TIME_SECONDS,
|
||||
"58": TIME_SECONDS,
|
||||
"59": "S/m",
|
||||
"60": "m_b",
|
||||
"61": "M_L",
|
||||
"62": "M_w",
|
||||
"63": "M_S",
|
||||
"64": "shindo",
|
||||
"65": "SML",
|
||||
"69": VOLUME_GALLONS,
|
||||
"71": UV_INDEX,
|
||||
"72": VOLT,
|
||||
"73": POWER_WATT,
|
||||
"74": f"{POWER_WATT}/{LENGTH_METERS}²",
|
||||
"75": "weekday",
|
||||
"76": DEGREE,
|
||||
"77": TIME_YEARS,
|
||||
"82": "mm",
|
||||
"83": LENGTH_KILOMETERS,
|
||||
"85": "Ω",
|
||||
"86": "kΩ",
|
||||
"87": f"{LENGTH_METERS}³/{LENGTH_METERS}³",
|
||||
"88": "Water activity",
|
||||
"89": "RPM",
|
||||
"90": FREQUENCY_HERTZ,
|
||||
"91": DEGREE,
|
||||
"92": f"{DEGREE} South",
|
||||
"101": f"{DEGREE} (x2)",
|
||||
"102": "kWs",
|
||||
"103": "$",
|
||||
"104": "¢",
|
||||
"105": LENGTH_INCHES,
|
||||
"106": "mm/day",
|
||||
}
|
||||
|
||||
UOM_TO_STATES = {
|
||||
"11": { # Deadbolt Status
|
||||
0: STATE_UNLOCKED,
|
||||
100: STATE_LOCKED,
|
||||
101: STATE_UNKNOWN,
|
||||
102: STATE_PROBLEM,
|
||||
},
|
||||
"15": { # Door Lock Alarm
|
||||
1: "master code changed",
|
||||
2: "tamper code entry limit",
|
||||
3: "escutcheon removed",
|
||||
4: "key/manually locked",
|
||||
5: "locked by touch",
|
||||
6: "key/manually unlocked",
|
||||
7: "remote locking jammed bolt",
|
||||
8: "remotely locked",
|
||||
9: "remotely unlocked",
|
||||
10: "deadbolt jammed",
|
||||
11: "battery too low to operate",
|
||||
12: "critical low battery",
|
||||
13: "low battery",
|
||||
14: "automatically locked",
|
||||
15: "automatic locking jammed bolt",
|
||||
16: "remotely power cycled",
|
||||
17: "lock handling complete",
|
||||
19: "user deleted",
|
||||
20: "user added",
|
||||
21: "duplicate pin",
|
||||
22: "jammed bolt by locking with keypad",
|
||||
23: "locked by keypad",
|
||||
24: "unlocked by keypad",
|
||||
25: "keypad attempt outside schedule",
|
||||
26: "hardware failure",
|
||||
27: "factory reset",
|
||||
},
|
||||
"66": { # Thermostat Heat/Cool State
|
||||
0: CURRENT_HVAC_IDLE,
|
||||
1: CURRENT_HVAC_HEAT,
|
||||
2: CURRENT_HVAC_COOL,
|
||||
3: CURRENT_HVAC_FAN,
|
||||
4: CURRENT_HVAC_HEAT, # Pending Heat
|
||||
5: CURRENT_HVAC_COOL, # Pending Cool
|
||||
# >6 defined in ISY but not implemented, leaving for future expanision.
|
||||
6: CURRENT_HVAC_IDLE,
|
||||
7: CURRENT_HVAC_HEAT,
|
||||
8: CURRENT_HVAC_HEAT,
|
||||
9: CURRENT_HVAC_COOL,
|
||||
10: CURRENT_HVAC_HEAT,
|
||||
11: CURRENT_HVAC_HEAT,
|
||||
},
|
||||
"67": { # Thermostat Mode
|
||||
0: HVAC_MODE_OFF,
|
||||
1: HVAC_MODE_HEAT,
|
||||
2: HVAC_MODE_COOL,
|
||||
3: HVAC_MODE_AUTO,
|
||||
4: PRESET_BOOST,
|
||||
5: "resume",
|
||||
6: HVAC_MODE_FAN_ONLY,
|
||||
7: "furnace",
|
||||
8: HVAC_MODE_DRY,
|
||||
9: "moist air",
|
||||
10: "auto changeover",
|
||||
11: "energy save heat",
|
||||
12: "energy save cool",
|
||||
13: PRESET_AWAY,
|
||||
14: HVAC_MODE_AUTO,
|
||||
15: HVAC_MODE_AUTO,
|
||||
16: HVAC_MODE_AUTO,
|
||||
},
|
||||
"68": { # Thermostat Fan Mode
|
||||
0: FAN_AUTO,
|
||||
1: FAN_ON,
|
||||
2: FAN_HIGH, # Auto High
|
||||
3: FAN_HIGH,
|
||||
4: FAN_MEDIUM, # Auto Medium
|
||||
5: FAN_MEDIUM,
|
||||
6: "circulation",
|
||||
7: "humidity circulation",
|
||||
},
|
||||
"78": {0: STATE_OFF, 100: STATE_ON}, # 0-Off 100-On
|
||||
"79": {0: STATE_OPEN, 100: STATE_CLOSED}, # 0-Open 100-Close
|
||||
"80": { # Thermostat Fan Run State
|
||||
0: STATE_OFF,
|
||||
1: STATE_ON,
|
||||
2: "on high",
|
||||
3: "on medium",
|
||||
4: "circulation",
|
||||
5: "humidity circulation",
|
||||
6: "right/left circulation",
|
||||
7: "up/down circulation",
|
||||
8: "quiet circulation",
|
||||
},
|
||||
"84": {0: SERVICE_LOCK, 1: SERVICE_UNLOCK}, # Secure Mode
|
||||
"93": { # Power Management Alarm
|
||||
1: "power applied",
|
||||
2: "ac mains disconnected",
|
||||
3: "ac mains reconnected",
|
||||
4: "surge detection",
|
||||
5: "volt drop or drift",
|
||||
6: "over current detected",
|
||||
7: "over voltage detected",
|
||||
8: "over load detected",
|
||||
9: "load error",
|
||||
10: "replace battery soon",
|
||||
11: "replace battery now",
|
||||
12: "battery is charging",
|
||||
13: "battery is fully charged",
|
||||
14: "charge battery soon",
|
||||
15: "charge battery now",
|
||||
},
|
||||
"94": { # Appliance Alarm
|
||||
1: "program started",
|
||||
2: "program in progress",
|
||||
3: "program completed",
|
||||
4: "replace main filter",
|
||||
5: "failure to set target temperature",
|
||||
6: "supplying water",
|
||||
7: "water supply failure",
|
||||
8: "boiling",
|
||||
9: "boiling failure",
|
||||
10: "washing",
|
||||
11: "washing failure",
|
||||
12: "rinsing",
|
||||
13: "rinsing failure",
|
||||
14: "draining",
|
||||
15: "draining failure",
|
||||
16: "spinning",
|
||||
17: "spinning failure",
|
||||
18: "drying",
|
||||
19: "drying failure",
|
||||
20: "fan failure",
|
||||
21: "compressor failure",
|
||||
},
|
||||
"95": { # Home Health Alarm
|
||||
1: "leaving bed",
|
||||
2: "sitting on bed",
|
||||
3: "lying on bed",
|
||||
4: "posture changed",
|
||||
5: "sitting on edge of bed",
|
||||
},
|
||||
"96": { # VOC Level
|
||||
1: "clean",
|
||||
2: "slightly polluted",
|
||||
3: "moderately polluted",
|
||||
4: "highly polluted",
|
||||
},
|
||||
"97": { # Barrier Status
|
||||
**{
|
||||
0: STATE_CLOSED,
|
||||
100: STATE_OPEN,
|
||||
101: STATE_UNKNOWN,
|
||||
102: "stopped",
|
||||
103: STATE_CLOSING,
|
||||
104: STATE_OPENING,
|
||||
},
|
||||
**{
|
||||
b: f"{b} %" for a, b in enumerate(list(range(1, 100)))
|
||||
}, # 1-99 are percentage open
|
||||
},
|
||||
"98": { # Insteon Thermostat Mode
|
||||
0: HVAC_MODE_OFF,
|
||||
1: HVAC_MODE_HEAT,
|
||||
2: HVAC_MODE_COOL,
|
||||
3: HVAC_MODE_HEAT_COOL,
|
||||
4: HVAC_MODE_FAN_ONLY,
|
||||
5: HVAC_MODE_AUTO, # Program Auto
|
||||
6: HVAC_MODE_AUTO, # Program Heat-Set @ Local Device Only
|
||||
7: HVAC_MODE_AUTO, # Program Cool-Set @ Local Device Only
|
||||
},
|
||||
"99": {7: FAN_ON, 8: FAN_AUTO}, # Insteon Thermostat Fan Mode
|
||||
}
|
||||
|
||||
ISY_BIN_SENS_DEVICE_TYPES = {
|
||||
"moisture": ["16.8.", "16.13.", "16.14."],
|
||||
"opening": ["16.9.", "16.6.", "16.7.", "16.2.", "16.17.", "16.20.", "16.21."],
|
||||
"motion": ["16.1.", "16.4.", "16.5.", "16.3.", "16.22."],
|
||||
"climate": ["5.11.", "5.10."],
|
||||
}
|
||||
|
||||
# TEMPORARY CONSTANTS -- REMOVE AFTER PyISYv2 IS AVAILABLE
|
||||
ISY_VALUE_UNKNOWN = -1 * float("inf")
|
|
@ -1,28 +1,12 @@
|
|||
"""Support for ISY994 covers."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.cover import DOMAIN, CoverEntity
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED,
|
||||
STATE_CLOSING,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.components.cover import DOMAIN as COVER, CoverEntity
|
||||
from homeassistant.const import STATE_CLOSED, STATE_OPEN
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALUE_TO_STATE = {
|
||||
0: STATE_CLOSED,
|
||||
101: STATE_UNKNOWN,
|
||||
102: "stopped",
|
||||
103: STATE_CLOSING,
|
||||
104: STATE_OPENING,
|
||||
}
|
||||
from .const import _LOGGER, UOM_TO_STATES
|
||||
|
||||
|
||||
def setup_platform(
|
||||
|
@ -30,10 +14,10 @@ def setup_platform(
|
|||
):
|
||||
"""Set up the ISY994 cover platform."""
|
||||
devices = []
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][COVER]:
|
||||
devices.append(ISYCoverEntity(node))
|
||||
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][DOMAIN]:
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][COVER]:
|
||||
devices.append(ISYCoverProgram(name, status, actions))
|
||||
|
||||
add_entities(devices)
|
||||
|
@ -59,7 +43,8 @@ class ISYCoverEntity(ISYDevice, CoverEntity):
|
|||
"""Get the state of the ISY994 cover device."""
|
||||
if self.is_unknown():
|
||||
return None
|
||||
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
|
||||
# TEMPORARY: Cast value to int until PyISYv2.
|
||||
return UOM_TO_STATES["97"].get(int(self.value), STATE_OPEN)
|
||||
|
||||
def open_cover(self, **kwargs) -> None:
|
||||
"""Send the open cover command to the ISY994 cover device."""
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
"""Support for ISY994 fans."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DOMAIN,
|
||||
DOMAIN as FAN,
|
||||
SPEED_HIGH,
|
||||
SPEED_LOW,
|
||||
SPEED_MEDIUM,
|
||||
|
@ -14,8 +13,7 @@ from homeassistant.components.fan import (
|
|||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import _LOGGER
|
||||
|
||||
VALUE_TO_STATE = {
|
||||
0: SPEED_OFF,
|
||||
|
@ -37,10 +35,10 @@ def setup_platform(
|
|||
"""Set up the ISY994 fan platform."""
|
||||
devices = []
|
||||
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][FAN]:
|
||||
devices.append(ISYFanDevice(node))
|
||||
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][DOMAIN]:
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][FAN]:
|
||||
devices.append(ISYFanProgram(name, status, actions))
|
||||
|
||||
add_entities(devices)
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
"""Sorting helpers for ISY994 device classifications."""
|
||||
from collections import namedtuple
|
||||
|
||||
from PyISY.Nodes import Group
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
|
||||
from homeassistant.components.fan import DOMAIN as FAN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
ISY994_NODES,
|
||||
ISY994_PROGRAMS,
|
||||
ISY994_WEATHER,
|
||||
ISY_GROUP_PLATFORM,
|
||||
KEY_ACTIONS,
|
||||
KEY_FOLDER,
|
||||
KEY_MY_PROGRAMS,
|
||||
KEY_STATUS,
|
||||
NODE_FILTERS,
|
||||
SUPPORTED_PLATFORMS,
|
||||
SUPPORTED_PROGRAM_PLATFORMS,
|
||||
)
|
||||
|
||||
WeatherNode = namedtuple("WeatherNode", ("status", "name", "uom"))
|
||||
|
||||
|
||||
def _check_for_node_def(
|
||||
hass: HomeAssistantType, node, single_platform: str = None
|
||||
) -> bool:
|
||||
"""Check if the node matches the node_def_id for any platforms.
|
||||
|
||||
This is only present on the 5.0 ISY firmware, and is the most reliable
|
||||
way to determine a device's type.
|
||||
"""
|
||||
if not hasattr(node, "node_def_id") or node.node_def_id is None:
|
||||
# Node doesn't have a node_def (pre 5.0 firmware most likely)
|
||||
return False
|
||||
|
||||
node_def_id = node.node_def_id
|
||||
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_def_id in NODE_FILTERS[platform]["node_def_id"]:
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
_LOGGER.warning("Unsupported node: %s, type: %s", node.name, node.type)
|
||||
return False
|
||||
|
||||
|
||||
def _check_for_insteon_type(
|
||||
hass: HomeAssistantType, node, single_platform: str = None
|
||||
) -> bool:
|
||||
"""Check if the node matches the Insteon type for any platforms.
|
||||
|
||||
This is for (presumably) every version of the ISY firmware, but only
|
||||
works for Insteon device. "Node Server" (v5+) and Z-Wave and others will
|
||||
not have a type.
|
||||
"""
|
||||
if not hasattr(node, "type") or node.type is None:
|
||||
# Node doesn't have a type (non-Insteon device most likely)
|
||||
return False
|
||||
|
||||
device_type = node.type
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if any(
|
||||
[
|
||||
device_type.startswith(t)
|
||||
for t in set(NODE_FILTERS[platform]["insteon_type"])
|
||||
]
|
||||
):
|
||||
|
||||
# Hacky special-case just for FanLinc, which has a light module
|
||||
# as one of its nodes. Note that this special-case is not necessary
|
||||
# on ISY 5.x firmware as it uses the superior NodeDefs method
|
||||
if platform == FAN and int(node.nid[-1]) == 1:
|
||||
hass.data[ISY994_NODES][LIGHT].append(node)
|
||||
return True
|
||||
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _check_for_uom_id(
|
||||
hass: HomeAssistantType, node, single_platform: str = None, uom_list: list = None
|
||||
) -> bool:
|
||||
"""Check if a node's uom matches any of the platforms uom filter.
|
||||
|
||||
This is used for versions of the ISY firmware that report uoms as a single
|
||||
ID. We can often infer what type of device it is by that ID.
|
||||
"""
|
||||
if not hasattr(node, "uom") or node.uom is None:
|
||||
# Node doesn't have a uom (Scenes for example)
|
||||
return False
|
||||
|
||||
node_uom = set(map(str.lower, node.uom))
|
||||
|
||||
if uom_list:
|
||||
if node_uom.intersection(uom_list):
|
||||
hass.data[ISY994_NODES][single_platform].append(node)
|
||||
return True
|
||||
else:
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_uom.intersection(NODE_FILTERS[platform]["uom"]):
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _check_for_states_in_uom(
|
||||
hass: HomeAssistantType, node, single_platform: str = None, states_list: list = None
|
||||
) -> bool:
|
||||
"""Check if a list of uoms matches two possible filters.
|
||||
|
||||
This is for versions of the ISY firmware that report uoms as a list of all
|
||||
possible "human readable" states. This filter passes if all of the possible
|
||||
states fit inside the given filter.
|
||||
"""
|
||||
if not hasattr(node, "uom") or node.uom is None:
|
||||
# Node doesn't have a uom (Scenes for example)
|
||||
return False
|
||||
|
||||
node_uom = set(map(str.lower, node.uom))
|
||||
|
||||
if states_list:
|
||||
if node_uom == set(states_list):
|
||||
hass.data[ISY994_NODES][single_platform].append(node)
|
||||
return True
|
||||
else:
|
||||
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
|
||||
for platform in platforms:
|
||||
if node_uom == set(NODE_FILTERS[platform]["states"]):
|
||||
hass.data[ISY994_NODES][platform].append(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _is_sensor_a_binary_sensor(hass: HomeAssistantType, node) -> bool:
|
||||
"""Determine if the given sensor node should be a binary_sensor."""
|
||||
if _check_for_node_def(hass, node, single_platform=BINARY_SENSOR):
|
||||
return True
|
||||
if _check_for_insteon_type(hass, node, single_platform=BINARY_SENSOR):
|
||||
return True
|
||||
|
||||
# For the next two checks, we're providing our own set of uoms that
|
||||
# represent on/off devices. This is because we can only depend on these
|
||||
# checks in the context of already knowing that this is definitely a
|
||||
# sensor device.
|
||||
if _check_for_uom_id(
|
||||
hass, node, single_platform=BINARY_SENSOR, uom_list=["2", "78"]
|
||||
):
|
||||
return True
|
||||
if _check_for_states_in_uom(
|
||||
hass, node, single_platform=BINARY_SENSOR, states_list=["on", "off"]
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _categorize_nodes(
|
||||
hass: HomeAssistantType, nodes, ignore_identifier: str, sensor_identifier: str
|
||||
) -> None:
|
||||
"""Sort the nodes to their proper platforms."""
|
||||
for (path, node) in nodes:
|
||||
ignored = ignore_identifier in path or ignore_identifier in node.name
|
||||
if ignored:
|
||||
# Don't import this node as a device at all
|
||||
continue
|
||||
|
||||
if isinstance(node, Group):
|
||||
hass.data[ISY994_NODES][ISY_GROUP_PLATFORM].append(node)
|
||||
continue
|
||||
|
||||
if sensor_identifier in path or sensor_identifier in node.name:
|
||||
# User has specified to treat this as a sensor. First we need to
|
||||
# determine if it should be a binary_sensor.
|
||||
if _is_sensor_a_binary_sensor(hass, node):
|
||||
continue
|
||||
|
||||
hass.data[ISY994_NODES][SENSOR].append(node)
|
||||
continue
|
||||
|
||||
# We have a bunch of different methods for determining the device type,
|
||||
# each of which works with different ISY firmware versions or device
|
||||
# family. The order here is important, from most reliable to least.
|
||||
if _check_for_node_def(hass, node):
|
||||
continue
|
||||
if _check_for_insteon_type(hass, node):
|
||||
continue
|
||||
if _check_for_uom_id(hass, node):
|
||||
continue
|
||||
if _check_for_states_in_uom(hass, node):
|
||||
continue
|
||||
|
||||
|
||||
def _categorize_programs(hass: HomeAssistantType, programs: dict) -> None:
|
||||
"""Categorize the ISY994 programs."""
|
||||
for platform in SUPPORTED_PROGRAM_PLATFORMS:
|
||||
try:
|
||||
folder = programs[KEY_MY_PROGRAMS][f"HA.{platform}"]
|
||||
except KeyError:
|
||||
continue
|
||||
for dtype, _, node_id in folder.children:
|
||||
if dtype != KEY_FOLDER:
|
||||
continue
|
||||
entity_folder = folder[node_id]
|
||||
try:
|
||||
status = entity_folder[KEY_STATUS]
|
||||
assert status.dtype == "program", "Not a program"
|
||||
if platform != BINARY_SENSOR:
|
||||
actions = entity_folder[KEY_ACTIONS]
|
||||
assert actions.dtype == "program", "Not a program"
|
||||
else:
|
||||
actions = None
|
||||
except (AttributeError, KeyError, AssertionError):
|
||||
_LOGGER.warning(
|
||||
"Program entity '%s' not loaded due "
|
||||
"to invalid folder structure.",
|
||||
entity_folder.name,
|
||||
)
|
||||
continue
|
||||
|
||||
entity = (entity_folder.name, status, actions)
|
||||
hass.data[ISY994_PROGRAMS][platform].append(entity)
|
||||
|
||||
|
||||
def _categorize_weather(hass: HomeAssistantType, climate) -> None:
|
||||
"""Categorize the ISY994 weather data."""
|
||||
climate_attrs = dir(climate)
|
||||
weather_nodes = [
|
||||
WeatherNode(
|
||||
getattr(climate, attr),
|
||||
attr.replace("_", " "),
|
||||
getattr(climate, f"{attr}_units"),
|
||||
)
|
||||
for attr in climate_attrs
|
||||
if f"{attr}_units" in climate_attrs
|
||||
]
|
||||
hass.data[ISY994_WEATHER].extend(weather_nodes)
|
|
@ -1,14 +1,16 @@
|
|||
"""Support for ISY994 lights."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS, LightEntity
|
||||
from homeassistant.components.light import (
|
||||
DOMAIN as LIGHT,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import _LOGGER
|
||||
|
||||
ATTR_LAST_BRIGHTNESS = "last_brightness"
|
||||
|
||||
|
@ -18,7 +20,7 @@ def setup_platform(
|
|||
):
|
||||
"""Set up the ISY994 light platform."""
|
||||
devices = []
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][LIGHT]:
|
||||
devices.append(ISYLightDevice(node))
|
||||
|
||||
add_entities(devices)
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
"""Support for ISY994 locks."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.lock import DOMAIN, LockEntity
|
||||
from homeassistant.components.lock import DOMAIN as LOCK, LockEntity
|
||||
from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import _LOGGER
|
||||
|
||||
VALUE_TO_STATE = {0: STATE_UNLOCKED, 100: STATE_LOCKED}
|
||||
|
||||
|
@ -18,10 +16,10 @@ def setup_platform(
|
|||
):
|
||||
"""Set up the ISY994 lock platform."""
|
||||
devices = []
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][LOCK]:
|
||||
devices.append(ISYLockDevice(node))
|
||||
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][DOMAIN]:
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][LOCK]:
|
||||
devices.append(ISYLockProgram(name, status, actions))
|
||||
|
||||
add_entities(devices)
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
"name": "Universal Devices ISY994",
|
||||
"documentation": "https://www.home-assistant.io/integrations/isy994",
|
||||
"requirements": ["PyISY==1.1.2"],
|
||||
"codeowners": ["@bdraco"]
|
||||
"codeowners": ["@bdraco", "@shbatm"]
|
||||
}
|
||||
|
|
|
@ -1,252 +1,12 @@
|
|||
"""Support for ISY994 sensors."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
FREQUENCY_HERTZ,
|
||||
LENGTH_CENTIMETERS,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_METERS,
|
||||
MASS_KILOGRAMS,
|
||||
POWER_WATT,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
TIME_DAYS,
|
||||
TIME_HOURS,
|
||||
TIME_MILLISECONDS,
|
||||
TIME_MINUTES,
|
||||
TIME_MONTHS,
|
||||
TIME_SECONDS,
|
||||
TIME_YEARS,
|
||||
UNIT_PERCENTAGE,
|
||||
UV_INDEX,
|
||||
VOLT,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISY994_WEATHER, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UOM_FRIENDLY_NAME = {
|
||||
"1": "amp",
|
||||
"3": f"btu/{TIME_HOURS}",
|
||||
"4": TEMP_CELSIUS,
|
||||
"5": LENGTH_CENTIMETERS,
|
||||
"6": "ft³",
|
||||
"7": f"ft³/{TIME_MINUTES}",
|
||||
"8": "m³",
|
||||
"9": TIME_DAYS,
|
||||
"10": TIME_DAYS,
|
||||
"12": "dB",
|
||||
"13": "dB A",
|
||||
"14": DEGREE,
|
||||
"16": "macroseismic",
|
||||
"17": TEMP_FAHRENHEIT,
|
||||
"18": "ft",
|
||||
"19": TIME_HOURS,
|
||||
"20": TIME_HOURS,
|
||||
"21": "abs. humidity (%)",
|
||||
"22": "rel. humidity (%)",
|
||||
"23": "inHg",
|
||||
"24": "in/hr",
|
||||
"25": "index",
|
||||
"26": "K",
|
||||
"27": "keyword",
|
||||
"28": MASS_KILOGRAMS,
|
||||
"29": "kV",
|
||||
"30": "kW",
|
||||
"31": "kPa",
|
||||
"32": SPEED_KILOMETERS_PER_HOUR,
|
||||
"33": "kWH",
|
||||
"34": "liedu",
|
||||
"35": "l",
|
||||
"36": "lx",
|
||||
"37": "mercalli",
|
||||
"38": LENGTH_METERS,
|
||||
"39": "m³/hr",
|
||||
"40": SPEED_METERS_PER_SECOND,
|
||||
"41": "mA",
|
||||
"42": TIME_MILLISECONDS,
|
||||
"43": "mV",
|
||||
"44": TIME_MINUTES,
|
||||
"45": TIME_MINUTES,
|
||||
"46": "mm/hr",
|
||||
"47": TIME_MONTHS,
|
||||
"48": SPEED_MILES_PER_HOUR,
|
||||
"49": SPEED_METERS_PER_SECOND,
|
||||
"50": "ohm",
|
||||
"51": UNIT_PERCENTAGE,
|
||||
"52": "lb",
|
||||
"53": "power factor",
|
||||
"54": CONCENTRATION_PARTS_PER_MILLION,
|
||||
"55": "pulse count",
|
||||
"57": TIME_SECONDS,
|
||||
"58": TIME_SECONDS,
|
||||
"59": "seimens/m",
|
||||
"60": "body wave magnitude scale",
|
||||
"61": "Ricter scale",
|
||||
"62": "moment magnitude scale",
|
||||
"63": "surface wave magnitude scale",
|
||||
"64": "shindo",
|
||||
"65": "SML",
|
||||
"69": "gal",
|
||||
"71": UV_INDEX,
|
||||
"72": VOLT,
|
||||
"73": POWER_WATT,
|
||||
"74": f"{POWER_WATT}/m²",
|
||||
"75": "weekday",
|
||||
"76": f"Wind Direction ({DEGREE})",
|
||||
"77": TIME_YEARS,
|
||||
"82": "mm",
|
||||
"83": LENGTH_KILOMETERS,
|
||||
"85": "ohm",
|
||||
"86": "kOhm",
|
||||
"87": "m³/m³",
|
||||
"88": "Water activity",
|
||||
"89": "RPM",
|
||||
"90": FREQUENCY_HERTZ,
|
||||
"91": f"{DEGREE} (Relative to North)",
|
||||
"92": f"{DEGREE} (Relative to South)",
|
||||
}
|
||||
|
||||
UOM_TO_STATES = {
|
||||
"11": {"0": "unlocked", "100": "locked", "102": "jammed"},
|
||||
"15": {
|
||||
"1": "master code changed",
|
||||
"2": "tamper code entry limit",
|
||||
"3": "escutcheon removed",
|
||||
"4": "key/manually locked",
|
||||
"5": "locked by touch",
|
||||
"6": "key/manually unlocked",
|
||||
"7": "remote locking jammed bolt",
|
||||
"8": "remotely locked",
|
||||
"9": "remotely unlocked",
|
||||
"10": "deadbolt jammed",
|
||||
"11": "battery too low to operate",
|
||||
"12": "critical low battery",
|
||||
"13": "low battery",
|
||||
"14": "automatically locked",
|
||||
"15": "automatic locking jammed bolt",
|
||||
"16": "remotely power cycled",
|
||||
"17": "lock handling complete",
|
||||
"19": "user deleted",
|
||||
"20": "user added",
|
||||
"21": "duplicate pin",
|
||||
"22": "jammed bolt by locking with keypad",
|
||||
"23": "locked by keypad",
|
||||
"24": "unlocked by keypad",
|
||||
"25": "keypad attempt outside schedule",
|
||||
"26": "hardware failure",
|
||||
"27": "factory reset",
|
||||
},
|
||||
"66": {
|
||||
"0": "idle",
|
||||
"1": "heating",
|
||||
"2": "cooling",
|
||||
"3": "fan only",
|
||||
"4": "pending heat",
|
||||
"5": "pending cool",
|
||||
"6": "vent",
|
||||
"7": "aux heat",
|
||||
"8": "2nd stage heating",
|
||||
"9": "2nd stage cooling",
|
||||
"10": "2nd stage aux heat",
|
||||
"11": "3rd stage aux heat",
|
||||
},
|
||||
"67": {
|
||||
"0": "off",
|
||||
"1": "heat",
|
||||
"2": "cool",
|
||||
"3": "auto",
|
||||
"4": "aux/emergency heat",
|
||||
"5": "resume",
|
||||
"6": "fan only",
|
||||
"7": "furnace",
|
||||
"8": "dry air",
|
||||
"9": "moist air",
|
||||
"10": "auto changeover",
|
||||
"11": "energy save heat",
|
||||
"12": "energy save cool",
|
||||
"13": "away",
|
||||
},
|
||||
"68": {
|
||||
"0": "auto",
|
||||
"1": "on",
|
||||
"2": "auto high",
|
||||
"3": "high",
|
||||
"4": "auto medium",
|
||||
"5": "medium",
|
||||
"6": "circulation",
|
||||
"7": "humidity circulation",
|
||||
},
|
||||
"93": {
|
||||
"1": "power applied",
|
||||
"2": "ac mains disconnected",
|
||||
"3": "ac mains reconnected",
|
||||
"4": "surge detection",
|
||||
"5": "volt drop or drift",
|
||||
"6": "over current detected",
|
||||
"7": "over voltage detected",
|
||||
"8": "over load detected",
|
||||
"9": "load error",
|
||||
"10": "replace battery soon",
|
||||
"11": "replace battery now",
|
||||
"12": "battery is charging",
|
||||
"13": "battery is fully charged",
|
||||
"14": "charge battery soon",
|
||||
"15": "charge battery now",
|
||||
},
|
||||
"94": {
|
||||
"1": "program started",
|
||||
"2": "program in progress",
|
||||
"3": "program completed",
|
||||
"4": "replace main filter",
|
||||
"5": "failure to set target temperature",
|
||||
"6": "supplying water",
|
||||
"7": "water supply failure",
|
||||
"8": "boiling",
|
||||
"9": "boiling failure",
|
||||
"10": "washing",
|
||||
"11": "washing failure",
|
||||
"12": "rinsing",
|
||||
"13": "rinsing failure",
|
||||
"14": "draining",
|
||||
"15": "draining failure",
|
||||
"16": "spinning",
|
||||
"17": "spinning failure",
|
||||
"18": "drying",
|
||||
"19": "drying failure",
|
||||
"20": "fan failure",
|
||||
"21": "compressor failure",
|
||||
},
|
||||
"95": {
|
||||
"1": "leaving bed",
|
||||
"2": "sitting on bed",
|
||||
"3": "lying on bed",
|
||||
"4": "posture changed",
|
||||
"5": "sitting on edge of bed",
|
||||
},
|
||||
"96": {
|
||||
"1": "clean",
|
||||
"2": "slightly polluted",
|
||||
"3": "moderately polluted",
|
||||
"4": "highly polluted",
|
||||
},
|
||||
"97": {
|
||||
"0": "closed",
|
||||
"100": "open",
|
||||
"102": "stopped",
|
||||
"103": "closing",
|
||||
"104": "opening",
|
||||
},
|
||||
}
|
||||
from .const import _LOGGER, UOM_FRIENDLY_NAME, UOM_TO_STATES
|
||||
|
||||
|
||||
def setup_platform(
|
||||
|
@ -255,7 +15,7 @@ def setup_platform(
|
|||
"""Set up the ISY994 sensor platform."""
|
||||
devices = []
|
||||
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][SENSOR]:
|
||||
_LOGGER.debug("Loading %s", node.name)
|
||||
devices.append(ISYSensorDevice(node))
|
||||
|
||||
|
@ -289,8 +49,9 @@ class ISYSensorDevice(ISYDevice):
|
|||
if len(self._node.uom) == 1:
|
||||
if self._node.uom[0] in UOM_TO_STATES:
|
||||
states = UOM_TO_STATES.get(self._node.uom[0])
|
||||
if self.value in states:
|
||||
return states.get(self.value)
|
||||
# TEMPORARY: Cast value to int until PyISYv2.
|
||||
if int(self.value) in states:
|
||||
return states.get(int(self.value))
|
||||
elif self._node.prec and self._node.prec != [0]:
|
||||
str_val = str(self.value)
|
||||
int_prec = int(self._node.prec)
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
"""Support for ISY994 switches."""
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import _LOGGER
|
||||
|
||||
|
||||
def setup_platform(
|
||||
|
@ -15,11 +13,11 @@ def setup_platform(
|
|||
):
|
||||
"""Set up the ISY994 switch platform."""
|
||||
devices = []
|
||||
for node in hass.data[ISY994_NODES][DOMAIN]:
|
||||
for node in hass.data[ISY994_NODES][SWITCH]:
|
||||
if not node.dimmable:
|
||||
devices.append(ISYSwitchDevice(node))
|
||||
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][DOMAIN]:
|
||||
for name, status, actions in hass.data[ISY994_PROGRAMS][SWITCH]:
|
||||
devices.append(ISYSwitchProgram(name, status, actions))
|
||||
|
||||
add_entities(devices)
|
||||
|
|
Loading…
Reference in New Issue