diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py
index 90e114e7023..27d81a671c8 100644
--- a/homeassistant/components/isy994/__init__.py
+++ b/homeassistant/components/isy994/__init__.py
@@ -1,16 +1,23 @@
"""Support the ISY-994 controllers."""
from __future__ import annotations
-from functools import partial
from urllib.parse import urlparse
-from pyisy import ISY
+from aiohttp import CookieJar
+import async_timeout
+from pyisy import ISY, ISYConnectionError, ISYInvalidAuthError, ISYResponseParseError
import voluptuous as vol
from homeassistant import config_entries
-from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.const import (
+ CONF_HOST,
+ CONF_PASSWORD,
+ CONF_USERNAME,
+ EVENT_HOMEASSISTANT_STOP,
+)
from homeassistant.core import HomeAssistant, callback
-from homeassistant.helpers import config_validation as cv
+from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.helpers import aiohttp_client, config_validation as cv
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.typing import ConfigType
@@ -32,7 +39,7 @@ from .const import (
ISY994_VARIABLES,
MANUFACTURER,
PLATFORMS,
- SUPPORTED_PROGRAM_PLATFORMS,
+ PROGRAM_PLATFORMS,
UNDO_UPDATE_LISTENER,
)
from .helpers import _categorize_nodes, _categorize_programs, _categorize_variables
@@ -115,7 +122,7 @@ async def async_setup_entry(
hass_isy_data[ISY994_NODES][platform] = []
hass_isy_data[ISY994_PROGRAMS] = {}
- for platform in SUPPORTED_PROGRAM_PLATFORMS:
+ for platform in PROGRAM_PLATFORMS:
hass_isy_data[ISY994_PROGRAMS][platform] = []
hass_isy_data[ISY994_VARIABLES] = []
@@ -139,31 +146,50 @@ async def async_setup_entry(
if host.scheme == "http":
https = False
port = host.port or 80
+ session = aiohttp_client.async_create_clientsession(
+ hass, verify_ssl=None, cookie_jar=CookieJar(unsafe=True)
+ )
elif host.scheme == "https":
https = True
port = host.port or 443
+ session = aiohttp_client.async_get_clientsession(hass)
else:
_LOGGER.error("The isy994 host value in configuration is invalid")
return False
# Connect to ISY controller.
- isy = await hass.async_add_executor_job(
- partial(
- ISY,
- host.hostname,
- port,
- username=user,
- password=password,
- use_https=https,
- tls_ver=tls_version,
- webroot=host.path,
- )
+ isy = ISY(
+ host.hostname,
+ port,
+ username=user,
+ password=password,
+ use_https=https,
+ tls_ver=tls_version,
+ webroot=host.path,
+ websession=session,
+ use_websocket=True,
)
- if not isy.connected:
- return False
- # Trigger a status update for all nodes, not done automatically in PyISY v2.x
- await hass.async_add_executor_job(isy.nodes.update)
+ try:
+ with async_timeout.timeout(30):
+ await isy.initialize()
+ except ISYInvalidAuthError as err:
+ _LOGGER.error(
+ "Invalid credentials for the ISY, please adjust settings and try again: %s",
+ err,
+ )
+ return False
+ except ISYConnectionError as err:
+ _LOGGER.error(
+ "Failed to connect to the ISY, please adjust settings and try again: %s",
+ err,
+ )
+ raise ConfigEntryNotReady from err
+ except ISYResponseParseError as err:
+ _LOGGER.warning(
+ "Error processing responses from the ISY; device may be busy, trying again later"
+ )
+ raise ConfigEntryNotReady from err
_categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier)
_categorize_programs(hass_isy_data, isy.programs)
@@ -181,13 +207,21 @@ async def async_setup_entry(
def _start_auto_update() -> None:
"""Start isy auto update."""
_LOGGER.debug("ISY Starting Event Stream and automatic updates")
- isy.auto_update = True
+ isy.websocket.start()
+
+ def _stop_auto_update(event) -> None:
+ """Stop the isy auto update on Home Assistant Shutdown."""
+ _LOGGER.debug("ISY Stopping Event Stream and automatic updates")
+ isy.websocket.stop()
await hass.async_add_executor_job(_start_auto_update)
undo_listener = entry.add_update_listener(_async_update_listener)
hass_isy_data[UNDO_UPDATE_LISTENER] = undo_listener
+ entry.async_on_unload(
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_auto_update)
+ )
# Register Integration-wide Services:
async_setup_services(hass)
@@ -248,9 +282,9 @@ async def async_unload_entry(
isy = hass_isy_data[ISY994_ISY]
def _stop_auto_update() -> None:
- """Start isy auto update."""
+ """Stop the isy auto update."""
_LOGGER.debug("ISY Stopping Event Stream and automatic updates")
- isy.auto_update = False
+ isy.websocket.stop()
await hass.async_add_executor_job(_stop_auto_update)
diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py
index c58c37edb42..4a259dac6d8 100644
--- a/homeassistant/components/isy994/binary_sensor.py
+++ b/homeassistant/components/isy994/binary_sensor.py
@@ -251,11 +251,11 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
"""Subscribe to the node and subnode event emitters."""
await super().async_added_to_hass()
- self._node.control_events.subscribe(self._positive_node_control_handler)
+ self._node.control_events.subscribe(self._async_positive_node_control_handler)
if self._negative_node is not None:
self._negative_node.control_events.subscribe(
- self._negative_node_control_handler
+ self._async_negative_node_control_handler
)
def add_heartbeat_device(self, device) -> None:
@@ -267,10 +267,10 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
"""
self._heartbeat_device = device
- def _heartbeat(self) -> None:
+ def _async_heartbeat(self) -> None:
"""Send a heartbeat to our heartbeat device, if we have one."""
if self._heartbeat_device is not None:
- self._heartbeat_device.heartbeat()
+ self._heartbeat_device.async_heartbeat()
def add_negative_node(self, child) -> None:
"""Add a negative node to this binary sensor device.
@@ -292,7 +292,8 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
# of the sensor until we receive our first ON event.
self._computed_state = None
- def _negative_node_control_handler(self, event: object) -> None:
+ @callback
+ def _async_negative_node_control_handler(self, event: object) -> None:
"""Handle an "On" control event from the "negative" node."""
if event.control == CMD_ON:
_LOGGER.debug(
@@ -300,10 +301,11 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
self.name,
)
self._computed_state = False
- self.schedule_update_ha_state()
- self._heartbeat()
+ self.async_write_ha_state()
+ self._async_heartbeat()
- def _positive_node_control_handler(self, event: object) -> None:
+ @callback
+ def _async_positive_node_control_handler(self, event: object) -> None:
"""Handle On and Off control event coming from the primary node.
Depending on device configuration, sometimes only On events
@@ -316,18 +318,19 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
self.name,
)
self._computed_state = True
- self.schedule_update_ha_state()
- self._heartbeat()
+ self.async_write_ha_state()
+ self._async_heartbeat()
if event.control == CMD_OFF:
_LOGGER.debug(
"Sensor %s turning Off via the Primary node sending a DOF command",
self.name,
)
self._computed_state = False
- self.schedule_update_ha_state()
- self._heartbeat()
+ self.async_write_ha_state()
+ self._async_heartbeat()
- def on_update(self, event: object) -> None:
+ @callback
+ def async_on_update(self, event: object) -> None:
"""Primary node status updates.
We MOSTLY ignore these updates, as we listen directly to the Control
@@ -340,8 +343,8 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
if self._status_was_unknown and self._computed_state is None:
self._computed_state = bool(self._node.status)
self._status_was_unknown = False
- self.schedule_update_ha_state()
- self._heartbeat()
+ self.async_write_ha_state()
+ self._async_heartbeat()
@property
def is_on(self) -> bool:
@@ -395,9 +398,10 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
The ISY uses both DON and DOF commands (alternating) for a heartbeat.
"""
if event.control in [CMD_ON, CMD_OFF]:
- self.heartbeat()
+ self.async_heartbeat()
- def heartbeat(self):
+ @callback
+ def async_heartbeat(self):
"""Mark the device as online, and restart the 25 hour timer.
This gets called when the heartbeat node beats, but also when the
@@ -407,7 +411,7 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
"""
self._computed_state = False
self._restart_timer()
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
def _restart_timer(self):
"""Restart the 25 hour timer."""
@@ -423,7 +427,7 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
"""Heartbeat missed; set state to ON to indicate dead battery."""
self._computed_state = True
self._heartbeat_timer = None
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
point_in_time = dt_util.utcnow() + timedelta(hours=25)
_LOGGER.debug(
@@ -436,7 +440,8 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
self.hass, timer_elapsed, point_in_time
)
- def on_update(self, event: object) -> None:
+ @callback
+ def async_on_update(self, event: object) -> None:
"""Ignore node status updates.
We listen directly to the Control events for this device.
diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py
index 5895a060db2..578fbe2bf21 100644
--- a/homeassistant/components/isy994/climate.py
+++ b/homeassistant/components/isy994/climate.py
@@ -203,7 +203,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
return None
return UOM_TO_STATES[UOM_FAN_MODES].get(fan_mode.value)
- def set_temperature(self, **kwargs) -> None:
+ async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
@@ -214,27 +214,27 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
if self.hvac_mode == HVAC_MODE_HEAT:
target_temp_low = target_temp
if target_temp_low is not None:
- self._node.set_climate_setpoint_heat(int(target_temp_low))
+ await self._node.set_climate_setpoint_heat(int(target_temp_low))
# Presumptive setting--event stream will correct if cmd fails:
self._target_temp_low = target_temp_low
if target_temp_high is not None:
- self._node.set_climate_setpoint_cool(int(target_temp_high))
+ await self._node.set_climate_setpoint_cool(int(target_temp_high))
# Presumptive setting--event stream will correct if cmd fails:
self._target_temp_high = target_temp_high
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
- def set_fan_mode(self, fan_mode: str) -> None:
+ async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
_LOGGER.debug("Requested fan mode %s", fan_mode)
- self._node.set_fan_mode(HA_FAN_TO_ISY.get(fan_mode))
+ await self._node.set_fan_mode(HA_FAN_TO_ISY.get(fan_mode))
# Presumptive setting--event stream will correct if cmd fails:
self._fan_mode = fan_mode
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
- def set_hvac_mode(self, hvac_mode: str) -> None:
+ async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
_LOGGER.debug("Requested operation mode %s", hvac_mode)
- self._node.set_climate_mode(HA_HVAC_TO_ISY.get(hvac_mode))
+ await self._node.set_climate_mode(HA_HVAC_TO_ISY.get(hvac_mode))
# Presumptive setting--event stream will correct if cmd fails:
self._hvac_mode = hvac_mode
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py
index 248d2d9c520..bd1baa66045 100644
--- a/homeassistant/components/isy994/config_flow.py
+++ b/homeassistant/components/isy994/config_flow.py
@@ -2,6 +2,9 @@
import logging
from urllib.parse import urlparse
+from aiohttp import CookieJar
+import async_timeout
+from pyisy import ISYConnectionError, ISYInvalidAuthError, ISYResponseParseError
from pyisy.configuration import Configuration
from pyisy.connection import Connection
import voluptuous as vol
@@ -11,6 +14,7 @@ from homeassistant.components import ssdp
from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
+from homeassistant.helpers import aiohttp_client
from .const import (
CONF_IGNORE_STRING,
@@ -57,25 +61,41 @@ async def validate_input(hass: core.HomeAssistant, data):
if host.scheme == "http":
https = False
port = host.port or 80
+ session = aiohttp_client.async_create_clientsession(
+ hass, verify_ssl=None, cookie_jar=CookieJar(unsafe=True)
+ )
elif host.scheme == "https":
https = True
port = host.port or 443
+ session = aiohttp_client.async_get_clientsession(hass)
else:
_LOGGER.error("The isy994 host value in configuration is invalid")
raise InvalidHost
# Connect to ISY controller.
- isy_conf = await hass.async_add_executor_job(
- _fetch_isy_configuration,
+ isy_conn = Connection(
host.hostname,
port,
user,
password,
- https,
- tls_version,
- host.path,
+ use_https=https,
+ tls_ver=tls_version,
+ webroot=host.path,
+ websession=session,
)
+ try:
+ with async_timeout.timeout(30):
+ isy_conf_xml = await isy_conn.test_connection()
+ except ISYInvalidAuthError as error:
+ raise InvalidAuth from error
+ except ISYConnectionError as error:
+ raise CannotConnect from error
+
+ try:
+ isy_conf = Configuration(xml=isy_conf_xml)
+ except ISYResponseParseError as error:
+ raise CannotConnect from error
if not isy_conf or "name" not in isy_conf or not isy_conf["name"]:
raise CannotConnect
@@ -83,26 +103,6 @@ async def validate_input(hass: core.HomeAssistant, data):
return {"title": f"{isy_conf['name']} ({host.hostname})", "uuid": isy_conf["uuid"]}
-def _fetch_isy_configuration(
- address, port, username, password, use_https, tls_ver, webroot
-):
- """Validate and fetch the configuration from the ISY."""
- try:
- isy_conn = Connection(
- address,
- port,
- username,
- password,
- use_https,
- tls_ver,
- webroot=webroot,
- )
- except ValueError as err:
- raise InvalidAuth(err.args[0]) from err
-
- return Configuration(xml=isy_conn.get_config())
-
-
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Universal Devices ISY994."""
diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py
index 9fdef92c84f..ed40c7eb289 100644
--- a/homeassistant/components/isy994/const.py
+++ b/homeassistant/components/isy994/const.py
@@ -130,7 +130,7 @@ KEY_ACTIONS = "actions"
KEY_STATUS = "status"
PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH, CLIMATE]
-SUPPORTED_PROGRAM_PLATFORMS = [BINARY_SENSOR, LOCK, FAN, COVER, SWITCH]
+PROGRAM_PLATFORMS = [BINARY_SENSOR, LOCK, FAN, COVER, SWITCH]
SUPPORTED_BIN_SENS_CLASSES = ["moisture", "opening", "motion", "climate"]
@@ -184,6 +184,7 @@ UNDO_UPDATE_LISTENER = "undo_update_listener"
# Used for discovery
UDN_UUID_PREFIX = "uuid:"
ISY_URL_POSTFIX = "/desc"
+EVENTS_SUFFIX = "_ISYSUB"
# Special Units of Measure
UOM_ISYV4_DEGREES = "degrees"
@@ -352,7 +353,7 @@ UOM_FRIENDLY_NAME = {
"22": "%RH",
"23": PRESSURE_INHG,
"24": SPEED_INCHES_PER_HOUR,
- UOM_INDEX: "index", # Index type. Use "node.formatted" for value
+ UOM_INDEX: UOM_INDEX, # Index type. Use "node.formatted" for value
"26": TEMP_KELVIN,
"27": "keyword",
"28": MASS_KILOGRAMS,
diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py
index 8bac6f50eb7..ca5432f4456 100644
--- a/homeassistant/components/isy994/cover.py
+++ b/homeassistant/components/isy994/cover.py
@@ -1,4 +1,5 @@
"""Support for ISY994 covers."""
+
from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.cover import (
@@ -67,23 +68,23 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
- def open_cover(self, **kwargs) -> None:
+ async def async_open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""
val = 100 if self._node.uom == UOM_BARRIER else None
- if not self._node.turn_on(val=val):
+ if not await self._node.turn_on(val=val):
_LOGGER.error("Unable to open the cover")
- def close_cover(self, **kwargs) -> None:
+ async def async_close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover device."""
- if not self._node.turn_off():
+ if not await self._node.turn_off():
_LOGGER.error("Unable to close the cover")
- def set_cover_position(self, **kwargs):
+ async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
if self._node.uom == UOM_8_BIT_RANGE:
position = round(position * 255.0 / 100.0)
- if not self._node.turn_on(val=position):
+ if not await self._node.turn_on(val=position):
_LOGGER.error("Unable to set cover position")
@@ -95,12 +96,12 @@ class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity):
"""Get whether the ISY994 cover program is closed."""
return bool(self._node.status)
- def open_cover(self, **kwargs) -> None:
+ async def async_open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover program."""
- if not self._actions.run_then():
+ if not await self._actions.run_then():
_LOGGER.error("Unable to open the cover")
- def close_cover(self, **kwargs) -> None:
+ async def async_close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover program."""
- if not self._actions.run_else():
+ if not await self._actions.run_else():
_LOGGER.error("Unable to close the cover")
diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py
index 25a2dc428a6..6dab5b2ed65 100644
--- a/homeassistant/components/isy994/entity.py
+++ b/homeassistant/components/isy994/entity.py
@@ -11,9 +11,11 @@ from pyisy.constants import (
from pyisy.helpers import NodeProperty
from homeassistant.const import STATE_OFF, STATE_ON
+from homeassistant.core import callback
+from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import Entity
-from .const import _LOGGER, DOMAIN
+from .const import DOMAIN
class ISYEntity(Entity):
@@ -30,16 +32,20 @@ class ISYEntity(Entity):
async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
- self._change_handler = self._node.status_events.subscribe(self.on_update)
+ self._change_handler = self._node.status_events.subscribe(self.async_on_update)
if hasattr(self._node, "control_events"):
- self._control_handler = self._node.control_events.subscribe(self.on_control)
+ self._control_handler = self._node.control_events.subscribe(
+ self.async_on_control
+ )
- def on_update(self, event: object) -> None:
+ @callback
+ def async_on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
- def on_control(self, event: NodeProperty) -> None:
+ @callback
+ def async_on_control(self, event: NodeProperty) -> None:
"""Handle a control event from the ISY994 Node."""
event_data = {
"entity_id": self.entity_id,
@@ -52,7 +58,7 @@ class ISYEntity(Entity):
if event.control not in EVENT_PROPS_IGNORED:
# New state attributes may be available, update the state.
- self.schedule_update_ha_state()
+ self.async_write_ha_state()
self.hass.bus.fire("isy994_control", event_data)
@@ -99,9 +105,9 @@ class ISYEntity(Entity):
f"ProductTypeID:{node.zwave_props.prod_type_id} "
f"ProductID:{node.zwave_props.product_id}"
)
- # Note: sw_version is not exposed by the ISY for the individual devices.
if hasattr(node, "folder") and node.folder is not None:
device_info["suggested_area"] = node.folder
+ # Note: sw_version is not exposed by the ISY for the individual devices.
return device_info
@@ -155,25 +161,23 @@ class ISYNodeEntity(ISYEntity):
self._attrs.update(attr)
return self._attrs
- def send_node_command(self, command):
+ async def async_send_node_command(self, command):
"""Respond to an entity service command call."""
if not hasattr(self._node, command):
- _LOGGER.error(
- "Invalid Service Call %s for device %s", command, self.entity_id
+ raise HomeAssistantError(
+ f"Invalid service call: {command} for device {self.entity_id}"
)
- return
- getattr(self._node, command)()
+ await getattr(self._node, command)()
- def send_raw_node_command(
+ async def async_send_raw_node_command(
self, command, value=None, unit_of_measurement=None, parameters=None
):
"""Respond to an entity service raw command call."""
if not hasattr(self._node, "send_cmd"):
- _LOGGER.error(
- "Invalid Service Call %s for device %s", command, self.entity_id
+ raise HomeAssistantError(
+ f"Invalid service call: {command} for device {self.entity_id}"
)
- return
- self._node.send_cmd(command, value, unit_of_measurement, parameters)
+ await self._node.send_cmd(command, value, unit_of_measurement, parameters)
class ISYProgramEntity(ISYEntity):
diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py
index 5d40eaef2a9..73b5bd683ba 100644
--- a/homeassistant/components/isy994/fan.py
+++ b/homeassistant/components/isy994/fan.py
@@ -65,17 +65,17 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
return None
return self._node.status != 0
- def set_percentage(self, percentage: int) -> None:
+ async def async_set_percentage(self, percentage: int) -> None:
"""Set node to speed percentage for the ISY994 fan device."""
if percentage == 0:
- self._node.turn_off()
+ await self._node.turn_off()
return
isy_speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage))
- self._node.turn_on(val=isy_speed)
+ await self._node.turn_on(val=isy_speed)
- def turn_on(
+ async def async_turn_on(
self,
speed: str = None,
percentage: int = None,
@@ -83,11 +83,11 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
**kwargs,
) -> None:
"""Send the turn on command to the ISY994 fan device."""
- self.set_percentage(percentage)
+ await self.async_set_percentage(percentage)
- def turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 fan device."""
- self._node.turn_off()
+ await self._node.turn_off()
@property
def supported_features(self) -> int:
@@ -108,8 +108,6 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
- if self._node.protocol == PROTO_INSTEON:
- return 3
return int_states_in_range(SPEED_RANGE)
@property
@@ -117,12 +115,12 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
"""Get if the fan is on."""
return self._node.status != 0
- def turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Send the turn on command to ISY994 fan program."""
- if not self._actions.run_then():
+ if not await self._actions.run_then():
_LOGGER.error("Unable to turn off the fan")
- def turn_on(
+ async def async_turn_on(
self,
speed: str = None,
percentage: int = None,
@@ -130,5 +128,5 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
**kwargs,
) -> None:
"""Send the turn off command to ISY994 fan program."""
- if not self._actions.run_else():
+ if not await self._actions.run_else():
_LOGGER.error("Unable to turn on the fan")
diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py
index 5322c8e0abf..b9b1a71901c 100644
--- a/homeassistant/components/isy994/helpers.py
+++ b/homeassistant/components/isy994/helpers.py
@@ -41,12 +41,12 @@ from .const import (
KEY_STATUS,
NODE_FILTERS,
PLATFORMS,
+ PROGRAM_PLATFORMS,
SUBNODE_CLIMATE_COOL,
SUBNODE_CLIMATE_HEAT,
SUBNODE_EZIO2X4_SENSORS,
SUBNODE_FANLINC_LIGHT,
SUBNODE_IOLINC_RELAY,
- SUPPORTED_PROGRAM_PLATFORMS,
TYPE_CATEGORY_SENSOR_ACTUATORS,
TYPE_EZIO2X4,
UOM_DOUBLE_TEMP,
@@ -167,7 +167,6 @@ def _check_for_zwave_cat(
device_type.startswith(t)
for t in set(NODE_FILTERS[platform][FILTER_ZWAVE_CAT])
):
-
hass_isy_data[ISY994_NODES][platform].append(node)
return True
@@ -314,7 +313,7 @@ def _categorize_nodes(
def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None:
"""Categorize the ISY994 programs."""
- for platform in SUPPORTED_PROGRAM_PLATFORMS:
+ for platform in PROGRAM_PLATFORMS:
folder = programs.get_by_name(f"{DEFAULT_PROGRAM_STRING}{platform}")
if not folder:
continue
diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py
index 73bd2f5934f..509fd259830 100644
--- a/homeassistant/components/isy994/light.py
+++ b/homeassistant/components/isy994/light.py
@@ -9,7 +9,7 @@ from homeassistant.components.light import (
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
-from homeassistant.core import HomeAssistant
+from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
@@ -72,30 +72,32 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
return round(self._node.status * 255.0 / 100.0)
return int(self._node.status)
- def turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 light device."""
self._last_brightness = self.brightness
- if not self._node.turn_off():
+ if not await self._node.turn_off():
_LOGGER.debug("Unable to turn off light")
- def on_update(self, event: object) -> None:
+ @callback
+ def async_on_update(self, event: object) -> None:
"""Save brightness in the update event from the ISY994 Node."""
if self._node.status not in (0, ISY_VALUE_UNKNOWN):
+ self._last_brightness = self._node.status
if self._node.uom == UOM_PERCENTAGE:
self._last_brightness = round(self._node.status * 255.0 / 100.0)
else:
self._last_brightness = self._node.status
- super().on_update(event)
+ super().async_on_update(event)
# pylint: disable=arguments-differ
- def turn_on(self, brightness=None, **kwargs) -> None:
+ async def async_turn_on(self, brightness=None, **kwargs) -> None:
"""Send the turn on command to the ISY994 light device."""
if self._restore_light_state and brightness is None and self._last_brightness:
brightness = self._last_brightness
# Special Case for ISY Z-Wave Devices using % instead of 0-255:
if brightness is not None and self._node.uom == UOM_PERCENTAGE:
brightness = round(brightness * 100.0 / 255.0)
- if not self._node.turn_on(val=brightness):
+ if not await self._node.turn_on(val=brightness):
_LOGGER.debug("Unable to turn on light")
@property
@@ -125,10 +127,10 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
):
self._last_brightness = last_state.attributes[ATTR_LAST_BRIGHTNESS]
- def set_on_level(self, value):
+ async def async_set_on_level(self, value):
"""Set the ON Level for a device."""
- self._node.set_on_level(value)
+ await self._node.set_on_level(value)
- def set_ramp_rate(self, value):
+ async def async_set_ramp_rate(self, value):
"""Set the Ramp Rate for a device."""
- self._node.set_ramp_rate(value)
+ await self._node.set_ramp_rate(value)
diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py
index 7e1296d2c86..c00a12d0096 100644
--- a/homeassistant/components/isy994/lock.py
+++ b/homeassistant/components/isy994/lock.py
@@ -1,4 +1,5 @@
"""Support for ISY994 locks."""
+
from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.lock import DOMAIN as LOCK, LockEntity
@@ -41,14 +42,14 @@ class ISYLockEntity(ISYNodeEntity, LockEntity):
return None
return VALUE_TO_STATE.get(self._node.status)
- def lock(self, **kwargs) -> None:
+ async def async_lock(self, **kwargs) -> None:
"""Send the lock command to the ISY994 device."""
- if not self._node.secure_lock():
+ if not await self._node.secure_lock():
_LOGGER.error("Unable to lock device")
- def unlock(self, **kwargs) -> None:
+ async def async_unlock(self, **kwargs) -> None:
"""Send the unlock command to the ISY994 device."""
- if not self._node.secure_unlock():
+ if not await self._node.secure_unlock():
_LOGGER.error("Unable to lock device")
@@ -60,12 +61,12 @@ class ISYLockProgramEntity(ISYProgramEntity, LockEntity):
"""Return true if the device is locked."""
return bool(self._node.status)
- def lock(self, **kwargs) -> None:
+ async def async_lock(self, **kwargs) -> None:
"""Lock the device."""
- if not self._actions.run_then():
+ if not await self._actions.run_then():
_LOGGER.error("Unable to lock device")
- def unlock(self, **kwargs) -> None:
+ async def async_unlock(self, **kwargs) -> None:
"""Unlock the device."""
- if not self._actions.run_else():
+ if not await self._actions.run_else():
_LOGGER.error("Unable to unlock device")
diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json
index a2326cbae5f..84f13ae4fc4 100644
--- a/homeassistant/components/isy994/manifest.json
+++ b/homeassistant/components/isy994/manifest.json
@@ -2,7 +2,7 @@
"domain": "isy994",
"name": "Universal Devices ISY994",
"documentation": "https://www.home-assistant.io/integrations/isy994",
- "requirements": ["pyisy==2.1.1"],
+ "requirements": ["pyisy==3.0.0"],
"codeowners": ["@bdraco", "@shbatm"],
"config_flow": true,
"ssdp": [
@@ -11,8 +11,6 @@
"deviceType": "urn:udi-com:device:X_Insteon_Lighting_Device:1"
}
],
- "dhcp": [
- {"hostname":"isy*", "macaddress":"0021B9*"}
- ],
+ "dhcp": [{ "hostname": "isy*", "macaddress": "0021B9*" }],
"iot_class": "local_push"
}
diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py
index 908bbdc72e8..79c5663f964 100644
--- a/homeassistant/components/isy994/sensor.py
+++ b/homeassistant/components/isy994/sensor.py
@@ -83,6 +83,10 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity):
if uom in [UOM_INDEX, UOM_ON_OFF]:
return self._node.formatted
+ # Check if this is an index type and get formatted value
+ if uom == UOM_INDEX and hasattr(self._node, "formatted"):
+ return self._node.formatted
+
# Handle ISY precision and rounding
value = convert_isy_value_to_hass(value, uom, self._node.prec)
@@ -123,7 +127,8 @@ class ISYSensorVariableEntity(ISYEntity, SensorEntity):
return {
"init_value": convert_isy_value_to_hass(
self._node.init, "", self._node.prec
- )
+ ),
+ "last_edited": self._node.last_edited,
}
@property
diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py
index 6d93b53b912..03ecc3930bb 100644
--- a/homeassistant/components/isy994/services.py
+++ b/homeassistant/components/isy994/services.py
@@ -27,7 +27,7 @@ from .const import (
ISY994_PROGRAMS,
ISY994_VARIABLES,
PLATFORMS,
- SUPPORTED_PROGRAM_PLATFORMS,
+ PROGRAM_PLATFORMS,
)
# Common Services for All Platforms:
@@ -183,12 +183,12 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
address,
isy.configuration["uuid"],
)
- await hass.async_add_executor_job(isy.query, address)
+ await isy.query(address)
return
_LOGGER.debug(
"Requesting system query of ISY %s", isy.configuration["uuid"]
)
- await hass.async_add_executor_job(isy.query)
+ await isy.query()
async def async_run_network_resource_service_handler(service):
"""Handle a network resource service call."""
@@ -208,10 +208,10 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
if name:
command = isy.networking.get_by_name(name)
if command is not None:
- await hass.async_add_executor_job(command.run)
+ await command.run()
return
_LOGGER.error(
- "Could not run network resource command. Not found or enabled on the ISY"
+ "Could not run network resource command; not found or enabled on the ISY"
)
async def async_send_program_command_service_handler(service):
@@ -231,9 +231,9 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
if name:
program = isy.programs.get_by_name(name)
if program is not None:
- await hass.async_add_executor_job(getattr(program, command))
+ await getattr(program, command)()
return
- _LOGGER.error("Could not send program command. Not found or enabled on the ISY")
+ _LOGGER.error("Could not send program command; not found or enabled on the ISY")
async def async_set_variable_service_handler(service):
"""Handle a set variable service call."""
@@ -254,9 +254,9 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
if address and vtype:
variable = isy.variables.vobjs[vtype].get(address)
if variable is not None:
- await hass.async_add_executor_job(variable.set_value, value, init)
+ await variable.set_value(value, init)
return
- _LOGGER.error("Could not set variable value. Not found or enabled on the ISY")
+ _LOGGER.error("Could not set variable value; not found or enabled on the ISY")
async def async_cleanup_registry_entries(service) -> None:
"""Remove extra entities that are no longer part of the integration."""
@@ -283,7 +283,7 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
if hasattr(node, "address"):
current_unique_ids.append(f"{uuid}_{node.address}")
- for platform in SUPPORTED_PROGRAM_PLATFORMS:
+ for platform in PROGRAM_PLATFORMS:
for _, node, _ in hass_isy_data[ISY994_PROGRAMS][platform]:
if hasattr(node, "address"):
current_unique_ids.append(f"{uuid}_{node.address}")
@@ -355,7 +355,7 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
async def _async_send_raw_node_command(call: ServiceCall):
await hass.helpers.service.entity_service_call(
- async_get_platforms(hass, DOMAIN), SERVICE_SEND_RAW_NODE_COMMAND, call
+ async_get_platforms(hass, DOMAIN), "async_send_raw_node_command", call
)
hass.services.async_register(
@@ -367,7 +367,7 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901
async def _async_send_node_command(call: ServiceCall):
await hass.helpers.service.entity_service_call(
- async_get_platforms(hass, DOMAIN), SERVICE_SEND_NODE_COMMAND, call
+ async_get_platforms(hass, DOMAIN), "async_send_node_command", call
)
hass.services.async_register(
@@ -408,8 +408,8 @@ def async_setup_light_services(hass: HomeAssistant):
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
- SERVICE_SET_ON_LEVEL, SERVICE_SET_VALUE_SCHEMA, SERVICE_SET_ON_LEVEL
+ SERVICE_SET_ON_LEVEL, SERVICE_SET_VALUE_SCHEMA, "async_set_on_level"
)
platform.async_register_entity_service(
- SERVICE_SET_RAMP_RATE, SERVICE_SET_RAMP_RATE_SCHEMA, SERVICE_SET_RAMP_RATE
+ SERVICE_SET_RAMP_RATE, SERVICE_SET_RAMP_RATE_SCHEMA, "async_set_ramp_rate"
)
diff --git a/homeassistant/components/isy994/services.yaml b/homeassistant/components/isy994/services.yaml
index 94d5a3cd89d..c163d78a173 100644
--- a/homeassistant/components/isy994/services.yaml
+++ b/homeassistant/components/isy994/services.yaml
@@ -57,19 +57,19 @@ send_node_command:
selector:
select:
options:
- - 'beep'
- - 'brighten'
- - 'dim'
- - 'disable'
- - 'enable'
- - 'fade_down'
- - 'fade_stop'
- - 'fade_up'
- - 'fast_off'
- - 'fast_on'
- - 'query'
+ - "beep"
+ - "brighten"
+ - "dim"
+ - "disable"
+ - "enable"
+ - "fade_down"
+ - "fade_stop"
+ - "fade_up"
+ - "fast_off"
+ - "fast_on"
+ - "query"
set_on_level:
- name: Set on level
+ name: Set On Level
description: Send a ISY set_on_level command to a Node.
target:
entity:
@@ -188,14 +188,14 @@ send_program_command:
selector:
select:
options:
- - 'disable'
- - 'disable_run_at_startup'
- - 'enable'
- - 'enable_run_at_startup'
- - 'run'
- - 'run_else'
- - 'run_then'
- - 'stop'
+ - "disable"
+ - "disable_run_at_startup"
+ - "enable"
+ - "enable_run_at_startup"
+ - "run"
+ - "run_else"
+ - "run_then"
+ - "stop"
isy:
name: ISY
description: If you have more than one ISY connected, provide the name of the ISY to query (as shown on the Device Registry or as the top-first node in the ISY Admin Console). If you have the same program name or address on multiple ISYs, omitting this will run the command on them all.
diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py
index 53056e45c7e..99bf6566b1b 100644
--- a/homeassistant/components/isy994/switch.py
+++ b/homeassistant/components/isy994/switch.py
@@ -1,4 +1,5 @@
"""Support for ISY994 switches."""
+
from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_GROUP
from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity
@@ -39,14 +40,14 @@ class ISYSwitchEntity(ISYNodeEntity, SwitchEntity):
return None
return bool(self._node.status)
- def turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 switch."""
- if not self._node.turn_off():
+ if not await self._node.turn_off():
_LOGGER.debug("Unable to turn off switch")
- def turn_on(self, **kwargs) -> None:
+ async def async_turn_on(self, **kwargs) -> None:
"""Send the turn on command to the ISY994 switch."""
- if not self._node.turn_on():
+ if not await self._node.turn_on():
_LOGGER.debug("Unable to turn on switch")
@property
@@ -65,14 +66,14 @@ class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity):
"""Get whether the ISY994 switch program is on."""
return bool(self._node.status)
- def turn_on(self, **kwargs) -> None:
+ async def async_turn_on(self, **kwargs) -> None:
"""Send the turn on command to the ISY994 switch program."""
- if not self._actions.run_then():
+ if not await self._actions.run_then():
_LOGGER.error("Unable to turn on switch")
- def turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 switch program."""
- if not self._actions.run_else():
+ if not await self._actions.run_else():
_LOGGER.error("Unable to turn off switch")
@property
diff --git a/requirements_all.txt b/requirements_all.txt
index 120926d5123..db8026faffd 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1491,7 +1491,7 @@ pyirishrail==0.0.2
pyiss==1.0.1
# homeassistant.components.isy994
-pyisy==2.1.1
+pyisy==3.0.0
# homeassistant.components.itach
pyitachip2ir==0.0.7
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 2894eea7c2d..dcca9bcb33f 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -826,7 +826,7 @@ pyipp==0.11.0
pyiqvia==0.3.1
# homeassistant.components.isy994
-pyisy==2.1.1
+pyisy==3.0.0
# homeassistant.components.kira
pykira==0.1.1
diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py
index 51750d42718..e5458a3c96b 100644
--- a/tests/components/isy994/test_config_flow.py
+++ b/tests/components/isy994/test_config_flow.py
@@ -1,10 +1,11 @@
"""Test the Universal Devices ISY994 config flow."""
-
+import re
from unittest.mock import patch
+from pyisy import ISYConnectionError, ISYInvalidAuthError
+
from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components import dhcp, ssdp
-from homeassistant.components.isy994.config_flow import CannotConnect
from homeassistant.components.isy994.const import (
CONF_IGNORE_STRING,
CONF_RESTORE_LIGHT_STATE,
@@ -63,12 +64,30 @@ MOCK_IMPORT_FULL_CONFIG = {
MOCK_DEVICE_NAME = "Name of the device"
MOCK_UUID = "ce:fb:72:31:b7:b9"
MOCK_MAC = "cefb7231b7b9"
-MOCK_VALIDATED_RESPONSE = {"name": MOCK_DEVICE_NAME, "uuid": MOCK_UUID}
-PATCH_CONFIGURATION = "homeassistant.components.isy994.config_flow.Configuration"
-PATCH_CONNECTION = "homeassistant.components.isy994.config_flow.Connection"
-PATCH_ASYNC_SETUP = "homeassistant.components.isy994.async_setup"
-PATCH_ASYNC_SETUP_ENTRY = "homeassistant.components.isy994.async_setup_entry"
+MOCK_CONFIG_RESPONSE = """
+
+ 5.0.16C
+ ISY-C-994
+
+ ce:fb:72:31:b7:b9
+ Name of the device
+
+
+
+ 21040
+ Networking Module
+ true
+ true
+
+
+
+"""
+
+INTEGRATION = "homeassistant.components.isy994"
+PATCH_CONNECTION = f"{INTEGRATION}.config_flow.Connection.test_connection"
+PATCH_ASYNC_SETUP = f"{INTEGRATION}.async_setup"
+PATCH_ASYNC_SETUP_ENTRY = f"{INTEGRATION}.async_setup_entry"
async def test_form(hass: HomeAssistant):
@@ -80,17 +99,12 @@ async def test_form(hass: HomeAssistant):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {}
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
PATCH_ASYNC_SETUP, return_value=True
) as mock_setup, patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
) as mock_setup_entry:
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,
@@ -129,9 +143,9 @@ async def test_form_invalid_auth(hass: HomeAssistant):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
- with patch(PATCH_CONFIGURATION), patch(
+ with patch(
PATCH_CONNECTION,
- side_effect=ValueError("PyISY could not connect to the ISY."),
+ side_effect=ISYInvalidAuthError(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -142,14 +156,52 @@ async def test_form_invalid_auth(hass: HomeAssistant):
assert result2["errors"] == {"base": "invalid_auth"}
-async def test_form_cannot_connect(hass: HomeAssistant):
- """Test we handle cannot connect error."""
+async def test_form_isy_connection_error(hass: HomeAssistant):
+ """Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
- with patch(PATCH_CONFIGURATION), patch(
+ with patch(
PATCH_CONNECTION,
- side_effect=CannotConnect,
+ side_effect=ISYConnectionError(),
+ ):
+ result2 = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ MOCK_USER_INPUT,
+ )
+
+ assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
+ assert result2["errors"] == {"base": "cannot_connect"}
+
+
+async def test_form_isy_parse_response_error(hass: HomeAssistant, caplog):
+ """Test we handle poorly formatted XML response from ISY."""
+ result = await hass.config_entries.flow.async_init(
+ DOMAIN, context={"source": config_entries.SOURCE_USER}
+ )
+ with patch(
+ PATCH_CONNECTION,
+ return_value=MOCK_CONFIG_RESPONSE.rsplit("\n", 3)[0], # Test with invalid XML
+ ):
+ result2 = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ MOCK_USER_INPUT,
+ )
+
+ assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
+ assert "ISY Could not parse response, poorly formatted XML." in caplog.text
+
+
+async def test_form_no_name_in_response(hass: HomeAssistant):
+ """Test we handle invalid response from ISY with name not set."""
+ result = await hass.config_entries.flow.async_init(
+ DOMAIN, context={"source": config_entries.SOURCE_USER}
+ )
+ with patch(
+ PATCH_CONNECTION,
+ return_value=re.sub(
+ r"\.*\n", "", MOCK_CONFIG_RESPONSE
+ ), # Test with line removed.
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -170,12 +222,7 @@ async def test_form_existing_config_entry(hass: HomeAssistant):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {}
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class:
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,
@@ -185,15 +232,12 @@ async def test_form_existing_config_entry(hass: HomeAssistant):
async def test_import_flow_some_fields(hass: HomeAssistant) -> None:
"""Test import config flow with just the basic fields."""
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(PATCH_ASYNC_SETUP, return_value=True), patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
+ PATCH_ASYNC_SETUP, return_value=True
+ ), patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
):
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
@@ -209,15 +253,12 @@ async def test_import_flow_some_fields(hass: HomeAssistant) -> None:
async def test_import_flow_with_https(hass: HomeAssistant) -> None:
"""Test import config with https."""
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(PATCH_ASYNC_SETUP, return_value=True), patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
+ PATCH_ASYNC_SETUP, return_value=True
+ ), patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
):
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
@@ -232,15 +273,12 @@ async def test_import_flow_with_https(hass: HomeAssistant) -> None:
async def test_import_flow_all_fields(hass: HomeAssistant) -> None:
"""Test import config flow with all fields."""
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(PATCH_ASYNC_SETUP, return_value=True), patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
+ PATCH_ASYNC_SETUP, return_value=True
+ ), patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
):
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
@@ -297,17 +335,12 @@ async def test_form_ssdp(hass: HomeAssistant):
assert result["step_id"] == "user"
assert result["errors"] == {}
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
PATCH_ASYNC_SETUP, return_value=True
) as mock_setup, patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
) as mock_setup_entry:
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,
@@ -339,17 +372,12 @@ async def test_form_dhcp(hass: HomeAssistant):
assert result["step_id"] == "user"
assert result["errors"] == {}
- with patch(PATCH_CONFIGURATION) as mock_config_class, patch(
- PATCH_CONNECTION
- ) as mock_connection_class, patch(
+ with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch(
PATCH_ASYNC_SETUP, return_value=True
) as mock_setup, patch(
PATCH_ASYNC_SETUP_ENTRY,
return_value=True,
) as mock_setup_entry:
- isy_conn = mock_connection_class.return_value
- isy_conn.get_config.return_value = None
- mock_config_class.return_value = MOCK_VALIDATED_RESPONSE
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,