Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions homeassistant/components/isy994/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CONF_PASSWORD,
CONF_USERNAME,
CONF_VARIABLES,
CONF_VERIFY_SSL,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
Expand All @@ -31,9 +32,9 @@
CONF_IGNORE_STRING,
CONF_NETWORK,
CONF_SENSOR_STRING,
CONF_TLS_VER,
DEFAULT_IGNORE_STRING,
DEFAULT_SENSOR_STRING,
DEFAULT_VERIFY_SSL,
DOMAIN,
ISY_CONF_FIRMWARE,
ISY_CONF_MODEL,
Expand Down Expand Up @@ -62,6 +63,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True


async def async_migrate_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
"""Migrate old config entries."""
if entry.version == 1 and entry.minor_version == 1:
new_data = {key: value for key, value in entry.data.items() if key != "tls"}
new_data.setdefault(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
hass.config_entries.async_update_entry(entry, data=new_data, minor_version=2)
Comment thread
shbatm marked this conversation as resolved.

return True
Comment on lines +66 to +73
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in a way I am like, by bumping the major version and removing the value, we kinda eliminate the ability to rollback for people, so I'd almost just avoid migrating

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will re-work it to just add the new config option and silently ignore the old tls_version key without migrating. Now that I'm looking at it again I remember doing something similar back in #80984 and not forcing a migration. Do you want the version to be tagged as a minor bump (e.g. 1.1) if I do that?

Copy link
Copy Markdown
Contributor Author

@shbatm shbatm May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked in b41568b to use MINOR_VERSION = 2 instead of bumping the major version — mirrors the pattern in yale_smart_alarm (drop legacy key + seed new key, no major bump, rollback preserved).

Comment on lines +66 to +73


async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
"""Set up the ISY 994 integration."""
isy_config = entry.data
Expand All @@ -71,9 +82,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
user = isy_config[CONF_USERNAME]
password = isy_config[CONF_PASSWORD]
host = urlparse(isy_config[CONF_HOST])

# Optional
tls_version = isy_config.get(CONF_TLS_VER)
verify_ssl = isy_config[CONF_VERIFY_SSL]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is contrary to reviewer's original suggestion to NOT use .get(). Holding for @joostlek's preference.

ignore_identifier = isy_options.get(CONF_IGNORE_STRING, DEFAULT_IGNORE_STRING)
sensor_identifier = isy_options.get(CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING)

Expand All @@ -86,7 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
elif host.scheme == SCHEME_HTTPS:
https = True
port = host.port or 443
session = aiohttp_client.async_get_clientsession(hass)
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=verify_ssl)
else:
_LOGGER.error("The ISY/IoX host value in configuration is invalid")
return False
Expand All @@ -98,7 +107,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsyConfigEntry) -> bool:
username=user,
password=password,
use_https=https,
tls_ver=tls_version,
verify_ssl=verify_ssl,
Comment thread
shbatm marked this conversation as resolved.
webroot=host.path,
websession=session,
use_websocket=True,
Expand Down
33 changes: 26 additions & 7 deletions homeassistant/components/isy994/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any
from urllib.parse import urlparse, urlunparse

import aiohttp
from aiohttp import CookieJar
from pyisy import ISYConnectionError, ISYInvalidAuthError, ISYResponseParseError
from pyisy.configuration import Configuration
Expand All @@ -18,7 +19,13 @@
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError
Expand All @@ -34,13 +41,12 @@
CONF_IGNORE_STRING,
CONF_RESTORE_LIGHT_STATE,
CONF_SENSOR_STRING,
CONF_TLS_VER,
CONF_VAR_SENSOR_STRING,
DEFAULT_IGNORE_STRING,
DEFAULT_RESTORE_LIGHT_STATE,
DEFAULT_SENSOR_STRING,
DEFAULT_TLS_VERSION,
DEFAULT_VAR_SENSOR_STRING,
DEFAULT_VERIFY_SSL,
DOMAIN,
HTTP_PORT,
HTTPS_PORT,
Expand All @@ -63,7 +69,7 @@ def _data_schema(schema_input: dict[str, str]) -> vol.Schema:
vol.Required(CONF_HOST, default=schema_input.get(CONF_HOST, "")): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_TLS_VER, default=DEFAULT_TLS_VERSION): vol.In([1.1, 1.2]),
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
},
extra=vol.ALLOW_EXTRA,
)
Expand All @@ -77,7 +83,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
user = data[CONF_USERNAME]
password = data[CONF_PASSWORD]
host = urlparse(data[CONF_HOST])
tls_version = data.get(CONF_TLS_VER)
verify_ssl = data[CONF_VERIFY_SSL]

if host.scheme == SCHEME_HTTP:
https = False
Expand All @@ -88,7 +94,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
elif host.scheme == SCHEME_HTTPS:
https = True
port = host.port or HTTPS_PORT
session = aiohttp_client.async_get_clientsession(hass)
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=verify_ssl)
else:
_LOGGER.error("The ISY/IoX host value in configuration is invalid")
raise InvalidHost
Expand All @@ -100,7 +106,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
user,
password,
use_https=https,
tls_ver=tls_version,
verify_ssl=verify_ssl,
webroot=host.path,
Comment thread
shbatm marked this conversation as resolved.
Comment thread
shbatm marked this conversation as resolved.
websession=session,
)
Expand All @@ -111,6 +117,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
except ISYInvalidAuthError as error:
raise InvalidAuth from error
except ISYConnectionError as error:
# pyisy chains the underlying aiohttp error via __cause__; ClientSSLError
# covers both protocol mismatch and certificate verification failures.
if isinstance(error.__cause__, aiohttp.ClientSSLError):
raise SslError from error
raise CannotConnect from error
Comment thread
shbatm marked this conversation as resolved.

try:
Expand All @@ -131,6 +141,7 @@ class Isy994ConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Universal Devices ISY/IoX."""

VERSION = 1
MINOR_VERSION = 2

def __init__(self) -> None:
"""Initialize the ISY/IoX config flow."""
Expand All @@ -156,6 +167,8 @@ async def async_step_user(
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except SslError:
errors["base"] = "ssl_error"
except InvalidHost:
errors["base"] = "invalid_host"
except InvalidAuth:
Expand Down Expand Up @@ -291,6 +304,8 @@ async def async_step_reauth_confirm(
await validate_input(self.hass, new_data)
except CannotConnect:
errors["base"] = "cannot_connect"
except SslError:
errors["base"] = "ssl_error"
except InvalidAuth:
errors[CONF_PASSWORD] = "invalid_auth"
else:
Expand Down Expand Up @@ -368,5 +383,9 @@ class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class SslError(HomeAssistantError):
"""Error to indicate a TLS/SSL handshake failure."""


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
3 changes: 1 addition & 2 deletions homeassistant/components/isy994/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,12 @@
CONF_IGNORE_STRING = "ignore_string"
CONF_SENSOR_STRING = "sensor_string"
CONF_VAR_SENSOR_STRING = "variable_sensor_string"
CONF_TLS_VER = "tls"
CONF_RESTORE_LIGHT_STATE = "restore_light_state"

DEFAULT_IGNORE_STRING = "{IGNORE ME}"
DEFAULT_SENSOR_STRING = "sensor"
DEFAULT_RESTORE_LIGHT_STATE = False
DEFAULT_TLS_VERSION = 1.1
DEFAULT_VERIFY_SSL = False
DEFAULT_PROGRAM_STRING = "HA."
DEFAULT_VAR_SENSOR_STRING = "HA."

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/isy994/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pyisy"],
"requirements": ["pyisy==3.5.1"],
"requirements": ["pyisy==3.6.1"],
"ssdp": [
{
"deviceType": "urn:udi-com:device:X_Insteon_Lighting_Device:1",
Expand Down
8 changes: 6 additions & 2 deletions homeassistant/components/isy994/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_host": "The host entry was not in full URL format, e.g., {sample_ip}",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"ssl_error": "TLS handshake failed. The controller may require a newer TLS version, or SSL verification may be failing due to a self-signed certificate.",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot doesn't like it's own suggested change...

"unknown": "[%key:common::config_flow::error::unknown%]"
},
"flow_title": "{name} ({host})",
Expand All @@ -25,8 +26,11 @@
"data": {
"host": "[%key:common::config_flow::data::url%]",
"password": "[%key:common::config_flow::data::password%]",
"tls": "The TLS version of the ISY controller.",
"username": "[%key:common::config_flow::data::username%]"
"username": "[%key:common::config_flow::data::username%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"verify_ssl": "Verify the controller's TLS certificate. Leave disabled for ISY-994/eisy/Polisy controllers using their default self-signed certificate."
},
"description": "The host entry must be in full URL format, e.g., {sample_ip}",
"title": "Connect to your ISY"
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading