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
34 changes: 24 additions & 10 deletions homeassistant/components/isy994/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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
Expand Down Expand Up @@ -38,9 +39,19 @@
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
Expand Down Expand Up @@ -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] = []

Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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,
)

Expand Down
68 changes: 63 additions & 5 deletions homeassistant/components/isy994/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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))

Expand Down Expand Up @@ -80,3 +102,39 @@ def __init__(self, node: Node, base_unique_id: str) -> None:
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]),
Comment thread
bdraco marked this conversation as resolved.
entry_type=DeviceEntryType.SERVICE,
)

async def async_press(self) -> None:
"""Press the button."""
await self._node.run()
13 changes: 10 additions & 3 deletions homeassistant/components/isy994/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
DOMAIN,
HTTP_PORT,
HTTPS_PORT,
ISY_CONF_NAME,
ISY_CONF_UUID,
ISY_URL_POSTFIX,
SCHEME_HTTP,
SCHEME_HTTPS,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -151,7 +156,9 @@ async def async_step_user(
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)

Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/isy994/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@
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"
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/isy994/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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):
Expand Down Expand Up @@ -73,7 +73,7 @@ def async_on_control(self, event: NodeProperty) -> None:
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)

Expand Down Expand Up @@ -127,7 +127,7 @@ def device_info(self) -> DeviceInfo | None:
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
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 @@ -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": [
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/isy994/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
DOMAIN as ISY994_DOMAIN,
ISY994_NODES,
ISY994_VARIABLES,
ISY_CONF_UUID,
SENSOR_AUX,
UOM_DOUBLE_TEMP,
UOM_FRIENDLY_NAME,
Expand Down Expand Up @@ -255,7 +256,7 @@ def unique_id(self) -> str | None:
"""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:
Expand Down
35 changes: 27 additions & 8 deletions homeassistant/components/isy994/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

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 (
Expand All @@ -23,7 +23,14 @@
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:
Expand Down Expand Up @@ -194,7 +201,7 @@ async def async_system_query_service_handler(service: ServiceCall) -> None:
_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(
Expand All @@ -204,21 +211,21 @@ async def async_system_query_service_handler(service: ServiceCall) -> None:
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(
hass,
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",
)
Expand All @@ -231,9 +238,9 @@ async def async_run_network_resource_service_handler(service: ServiceCall) -> No

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:
Expand All @@ -242,6 +249,18 @@ async def async_run_network_resource_service_handler(service: ServiceCall) -> No
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"
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/isy994/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading