diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index f0a40a03cfacce75864070fbd49b7a50aa21e7e7..be2948c7aa2b4c469146a0711c647a4691394ab0 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -7,6 +7,7 @@ from urllib.parse import urlparse from aiohttp import CookieJar import async_timeout from pyisy import ISY, ISYConnectionError, ISYInvalidAuthError, ISYResponseParseError +from pyisy.constants import PROTO_NETWORK_RESOURCE import voluptuous as vol from homeassistant import config_entries @@ -38,9 +39,19 @@ from .const import ( ISY994_NODES, ISY994_PROGRAMS, ISY994_VARIABLES, + ISY_CONF_FIRMWARE, + ISY_CONF_MODEL, + ISY_CONF_NAME, + ISY_CONF_NETWORKING, + ISY_CONF_UUID, + ISY_CONN_ADDRESS, + ISY_CONN_PORT, + ISY_CONN_TLS, MANUFACTURER, PLATFORMS, PROGRAM_PLATFORMS, + SCHEME_HTTP, + SCHEME_HTTPS, SENSOR_AUX, ) from .helpers import _categorize_nodes, _categorize_programs, _categorize_variables @@ -122,7 +133,7 @@ async def async_setup_entry( hass.data[DOMAIN][entry.entry_id] = {} hass_isy_data = hass.data[DOMAIN][entry.entry_id] - hass_isy_data[ISY994_NODES] = {SENSOR_AUX: []} + hass_isy_data[ISY994_NODES] = {SENSOR_AUX: [], PROTO_NETWORK_RESOURCE: []} for platform in PLATFORMS: hass_isy_data[ISY994_NODES][platform] = [] @@ -148,13 +159,13 @@ async def async_setup_entry( CONF_VAR_SENSOR_STRING, DEFAULT_VAR_SENSOR_STRING ) - if host.scheme == "http": + if host.scheme == SCHEME_HTTP: https = False port = host.port or 80 session = aiohttp_client.async_create_clientsession( hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True) ) - elif host.scheme == "https": + elif host.scheme == SCHEME_HTTPS: https = True port = host.port or 443 session = aiohttp_client.async_get_clientsession(hass) @@ -202,6 +213,9 @@ async def async_setup_entry( _categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier) _categorize_programs(hass_isy_data, isy.programs) _categorize_variables(hass_isy_data, isy.variables, variable_identifier) + if isy.configuration[ISY_CONF_NETWORKING]: + for resource in isy.networking.nobjs: + hass_isy_data[ISY994_NODES][PROTO_NETWORK_RESOURCE].append(resource) # Dump ISY Clock Information. Future: Add ISY as sensor to Hass with attrs _LOGGER.info(repr(isy.clock)) @@ -262,8 +276,8 @@ def _async_import_options_from_data_if_missing( def _async_isy_to_configuration_url(isy: ISY) -> str: """Extract the configuration url from the isy.""" connection_info = isy.conn.connection_info - proto = "https" if "tls" in connection_info else "http" - return f"{proto}://{connection_info['addr']}:{connection_info['port']}" + proto = SCHEME_HTTPS if ISY_CONN_TLS in connection_info else SCHEME_HTTP + return f"{proto}://{connection_info[ISY_CONN_ADDRESS]}:{connection_info[ISY_CONN_PORT]}" @callback @@ -274,12 +288,12 @@ def _async_get_or_create_isy_device_in_registry( url = _async_isy_to_configuration_url(isy) device_registry.async_get_or_create( config_entry_id=entry.entry_id, - connections={(dr.CONNECTION_NETWORK_MAC, isy.configuration["uuid"])}, - identifiers={(DOMAIN, isy.configuration["uuid"])}, + connections={(dr.CONNECTION_NETWORK_MAC, isy.configuration[ISY_CONF_UUID])}, + identifiers={(DOMAIN, isy.configuration[ISY_CONF_UUID])}, manufacturer=MANUFACTURER, - name=isy.configuration["name"], - model=isy.configuration["model"], - sw_version=isy.configuration["firmware"], + name=isy.configuration[ISY_CONF_NAME], + model=isy.configuration[ISY_CONF_MODEL], + sw_version=isy.configuration[ISY_CONF_FIRMWARE], configuration_url=url, ) diff --git a/homeassistant/components/isy994/button.py b/homeassistant/components/isy994/button.py index 0325c501d63f1e8ef39a8145bb3e37148185c4e4..7dbddafda24e7ba0faea4bdc1b32b79bb295da86 100644 --- a/homeassistant/components/isy994/button.py +++ b/homeassistant/components/isy994/button.py @@ -2,17 +2,29 @@ from __future__ import annotations from pyisy import ISY -from pyisy.constants import PROTO_INSTEON +from pyisy.constants import PROTO_INSTEON, PROTO_NETWORK_RESOURCE from pyisy.nodes import Node from homeassistant.components.button import ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN as ISY994_DOMAIN, ISY994_ISY, ISY994_NODES +from . import _async_isy_to_configuration_url +from .const import ( + DOMAIN as ISY994_DOMAIN, + ISY994_ISY, + ISY994_NODES, + ISY_CONF_FIRMWARE, + ISY_CONF_MODEL, + ISY_CONF_NAME, + ISY_CONF_NETWORKING, + ISY_CONF_UUID, + MANUFACTURER, +) async def async_setup_entry( @@ -23,13 +35,23 @@ async def async_setup_entry( """Set up ISY/IoX button from config entry.""" hass_isy_data = hass.data[ISY994_DOMAIN][config_entry.entry_id] isy: ISY = hass_isy_data[ISY994_ISY] - uuid = isy.configuration["uuid"] - entities: list[ISYNodeQueryButtonEntity | ISYNodeBeepButtonEntity] = [] - for node in hass_isy_data[ISY994_NODES][Platform.BUTTON]: + uuid = isy.configuration[ISY_CONF_UUID] + entities: list[ + ISYNodeQueryButtonEntity + | ISYNodeBeepButtonEntity + | ISYNetworkResourceButtonEntity + ] = [] + nodes: dict = hass_isy_data[ISY994_NODES] + for node in nodes[Platform.BUTTON]: entities.append(ISYNodeQueryButtonEntity(node, f"{uuid}_{node.address}")) if node.protocol == PROTO_INSTEON: entities.append(ISYNodeBeepButtonEntity(node, f"{uuid}_{node.address}")) + for node in nodes[PROTO_NETWORK_RESOURCE]: + entities.append( + ISYNetworkResourceButtonEntity(node, f"{uuid}_{PROTO_NETWORK_RESOURCE}") + ) + # Add entity to query full system entities.append(ISYNodeQueryButtonEntity(isy, uuid)) @@ -80,3 +102,39 @@ class ISYNodeBeepButtonEntity(ButtonEntity): async def async_press(self) -> None: """Press the button.""" await self._node.beep() + + +class ISYNetworkResourceButtonEntity(ButtonEntity): + """Representation of an ISY/IoX Network Resource button entity.""" + + _attr_should_poll = False + _attr_has_entity_name = True + + def __init__(self, node: Node, base_unique_id: str) -> None: + """Initialize an ISY network resource button entity.""" + self._node = node + + # Entity class attributes + self._attr_name = node.name + self._attr_unique_id = f"{base_unique_id}_{node.address}" + url = _async_isy_to_configuration_url(node.isy) + config = node.isy.configuration + self._attr_device_info = DeviceInfo( + identifiers={ + ( + ISY994_DOMAIN, + f"{config[ISY_CONF_UUID]}_{PROTO_NETWORK_RESOURCE}", + ) + }, + manufacturer=MANUFACTURER, + name=f"{config[ISY_CONF_NAME]} {ISY_CONF_NETWORKING}", + model=config[ISY_CONF_MODEL], + sw_version=config[ISY_CONF_FIRMWARE], + configuration_url=url, + via_device=(ISY994_DOMAIN, config[ISY_CONF_UUID]), + entry_type=DeviceEntryType.SERVICE, + ) + + async def async_press(self) -> None: + """Press the button.""" + await self._node.run() diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 3c058689bf849010aa13f2b50ca2da533ae70c7c..908bf710bf436c58e40355229f1b25049522214c 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -34,6 +34,8 @@ from .const import ( DOMAIN, HTTP_PORT, HTTPS_PORT, + ISY_CONF_NAME, + ISY_CONF_UUID, ISY_URL_POSTFIX, SCHEME_HTTP, SCHEME_HTTPS, @@ -106,11 +108,14 @@ async def validate_input( 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"]: + if not isy_conf or ISY_CONF_NAME not in isy_conf or not isy_conf[ISY_CONF_NAME]: raise CannotConnect # Return info that you want to store in the config entry. - return {"title": f"{isy_conf['name']} ({host.hostname})", "uuid": isy_conf["uuid"]} + return { + "title": f"{isy_conf[ISY_CONF_NAME]} ({host.hostname})", + ISY_CONF_UUID: isy_conf[ISY_CONF_UUID], + } class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -151,7 +156,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" if not errors: - await self.async_set_unique_id(info["uuid"], raise_on_progress=False) + await self.async_set_unique_id( + info[ISY_CONF_UUID], raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry(title=info["title"], data=user_input) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index de064bff312ef3375a3bfb735a918269e9e2acb0..402086ddec11bb3f2cad9854c07b7e611365371b 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -104,6 +104,16 @@ ISY994_NODES = "isy994_nodes" ISY994_PROGRAMS = "isy994_programs" ISY994_VARIABLES = "isy994_variables" +ISY_CONF_NETWORKING = "Networking Module" +ISY_CONF_UUID = "uuid" +ISY_CONF_NAME = "name" +ISY_CONF_MODEL = "model" +ISY_CONF_FIRMWARE = "firmware" + +ISY_CONN_PORT = "port" +ISY_CONN_ADDRESS = "addr" +ISY_CONN_TLS = "tls" + FILTER_UOM = "uom" FILTER_STATES = "states" FILTER_NODE_DEF_ID = "node_def_id" diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index f5633167cd10ba994343d099b62f7df9fe837014..144ec016d22d33047361f175067c234813125135 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -21,7 +21,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo, Entity from . import _async_isy_to_configuration_url -from .const import DOMAIN +from .const import DOMAIN, ISY_CONF_UUID class ISYEntity(Entity): @@ -73,7 +73,7 @@ class ISYEntity(Entity): def device_info(self) -> DeviceInfo | None: """Return the device_info of the device.""" isy = self._node.isy - uuid = isy.configuration["uuid"] + uuid = isy.configuration[ISY_CONF_UUID] node = self._node url = _async_isy_to_configuration_url(isy) @@ -127,7 +127,7 @@ class ISYEntity(Entity): def unique_id(self) -> str | None: """Get the unique identifier of the device.""" if hasattr(self._node, "address"): - return f"{self._node.isy.configuration['uuid']}_{self._node.address}" + return f"{self._node.isy.configuration[ISY_CONF_UUID]}_{self._node.address}" return None @property diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 8370f4ace481df7075bda22a1dcc00105fdb86e7..4cab67c7d96559f83b2a5252245bbd87f8a07f02 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -3,7 +3,7 @@ "name": "Universal Devices ISY/IoX", "integration_type": "hub", "documentation": "https://www.home-assistant.io/integrations/isy994", - "requirements": ["pyisy==3.0.11"], + "requirements": ["pyisy==3.0.12"], "codeowners": ["@bdraco", "@shbatm"], "config_flow": true, "ssdp": [ diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 4a80229eef7e2b7645e716f5870a8691b8a74adb..727600edea2802bc37d9dc6f792c4ad19be6f275 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -36,6 +36,7 @@ from .const import ( DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_VARIABLES, + ISY_CONF_UUID, SENSOR_AUX, UOM_DOUBLE_TEMP, UOM_FRIENDLY_NAME, @@ -255,7 +256,7 @@ class ISYAuxSensorEntity(ISYSensorEntity): """Get the unique identifier of the device and aux sensor.""" if not hasattr(self._node, "address"): return None - return f"{self._node.isy.configuration['uuid']}_{self._node.address}_{self._control}" + return f"{self._node.isy.configuration[ISY_CONF_UUID]}_{self._node.address}_{self._control}" @property def name(self) -> str: diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index 6825cab7cd20d750d5906b57126e14c84c7a4c22..ff7fdb965c71d5f22cbf0f1e284fb601961ba171 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from pyisy.constants import COMMAND_FRIENDLY_NAME +from pyisy.constants import COMMAND_FRIENDLY_NAME, PROTO_NETWORK_RESOURCE import voluptuous as vol from homeassistant.const import ( @@ -23,7 +23,14 @@ import homeassistant.helpers.entity_registry as er from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.service import entity_service_call -from .const import _LOGGER, DOMAIN, ISY994_ISY +from .const import ( + _LOGGER, + DOMAIN, + ISY994_ISY, + ISY_CONF_NAME, + ISY_CONF_NETWORKING, + ISY_CONF_UUID, +) from .util import unique_ids_for_config_entry_id # Common Services for All Platforms: @@ -194,7 +201,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 _LOGGER.debug( "Requesting query of device %s on ISY %s", address, - isy.configuration["uuid"], + isy.configuration[ISY_CONF_UUID], ) await isy.query(address) async_log_deprecated_service_call( @@ -204,13 +211,13 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 alternate_target=entity_registry.async_get_entity_id( Platform.BUTTON, DOMAIN, - f"{isy.configuration['uuid']}_{address}_query", + f"{isy.configuration[ISY_CONF_UUID]}_{address}_query", ), breaks_in_ha_version="2023.5.0", ) return _LOGGER.debug( - "Requesting system query of ISY %s", isy.configuration["uuid"] + "Requesting system query of ISY %s", isy.configuration[ISY_CONF_UUID] ) await isy.query() async_log_deprecated_service_call( @@ -218,7 +225,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 call=service, alternate_service="button.press", alternate_target=entity_registry.async_get_entity_id( - Platform.BUTTON, DOMAIN, f"{isy.configuration['uuid']}_query" + Platform.BUTTON, DOMAIN, f"{isy.configuration[ISY_CONF_UUID]}_query" ), breaks_in_ha_version="2023.5.0", ) @@ -231,9 +238,9 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and isy_name != isy.configuration["name"]: + if isy_name and isy_name != isy.configuration[ISY_CONF_NAME]: continue - if not hasattr(isy, "networking") or isy.networking is None: + if isy.networking is None or not isy.configuration[ISY_CONF_NETWORKING]: continue command = None if address: @@ -242,6 +249,18 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 command = isy.networking.get_by_name(name) if command is not None: await command.run() + entity_registry = er.async_get(hass) + async_log_deprecated_service_call( + hass, + call=service, + alternate_service="button.press", + alternate_target=entity_registry.async_get_entity_id( + Platform.BUTTON, + DOMAIN, + f"{isy.configuration[ISY_CONF_UUID]}_{PROTO_NETWORK_RESOURCE}_{address}", + ), + breaks_in_ha_version="2023.5.0", + ) return _LOGGER.error( "Could not run network resource command; not found or enabled on the ISY" diff --git a/homeassistant/components/isy994/services.yaml b/homeassistant/components/isy994/services.yaml index 90715b162d702cde1dec76d35b15c7f05a23d3c9..8d1aa8c58ef5cbfd73d8fc92154c9c0ee0c0d5ab 100644 --- a/homeassistant/components/isy994/services.yaml +++ b/homeassistant/components/isy994/services.yaml @@ -267,8 +267,8 @@ send_program_command: selector: text: run_network_resource: - name: Run network resource - description: Run a network resource on the ISY. + name: Run network resource (Deprecated) + description: "Run a network resource on the ISY. Deprecated: Use Network Resource button entity." fields: address: name: Address diff --git a/homeassistant/components/isy994/util.py b/homeassistant/components/isy994/util.py index 196801c58cea349e7ccc5314992ab854f1f77a47..45bf10cc1bd6fd5714c2594c76e382187cf452a8 100644 --- a/homeassistant/components/isy994/util.py +++ b/homeassistant/components/isy994/util.py @@ -9,6 +9,7 @@ from .const import ( ISY994_NODES, ISY994_PROGRAMS, ISY994_VARIABLES, + ISY_CONF_UUID, PLATFORMS, PROGRAM_PLATFORMS, ) @@ -19,7 +20,7 @@ def unique_ids_for_config_entry_id( ) -> set[str]: """Find all the unique ids for a config entry id.""" hass_isy_data = hass.data[DOMAIN][config_entry_id] - uuid = hass_isy_data[ISY994_ISY].configuration["uuid"] + uuid = hass_isy_data[ISY994_ISY].configuration[ISY_CONF_UUID] current_unique_ids: set[str] = {uuid} for platform in PLATFORMS: diff --git a/requirements_all.txt b/requirements_all.txt index 9c3d97760c7e62120351b2a82a0354b44f1ec807..cb210058118aa2ce0e03d3dafbdf8d0444c71f6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1693,7 +1693,7 @@ pyirishrail==0.0.2 pyiss==1.0.1 # homeassistant.components.isy994 -pyisy==3.0.11 +pyisy==3.0.12 # homeassistant.components.itach pyitachip2ir==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 02204bb6928ada91123d0daa780350365aac3a30..f953ab2452e80cdfbb2708fe330bbff5fc436d03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1206,7 +1206,7 @@ pyiqvia==2022.04.0 pyiss==1.0.1 # homeassistant.components.isy994 -pyisy==3.0.11 +pyisy==3.0.12 # homeassistant.components.kaleidescape pykaleidescape==1.0.1