mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Add query button entities to ISY994 devices and hub (#85337)
This commit is contained in:
parent
2976f843b5
commit
cf3ca816a8
56
homeassistant/components/isy994/button.py
Normal file
56
homeassistant/components/isy994/button.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""Representation of ISY/IoX buttons."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pyisy import ISY
|
||||
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.entity import DeviceInfo, EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN as ISY994_DOMAIN, ISY994_ISY, ISY994_NODES
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""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] = []
|
||||
for node in hass_isy_data[ISY994_NODES][Platform.BUTTON]:
|
||||
entities.append(ISYNodeQueryButtonEntity(node, f"{uuid}_{node.address}"))
|
||||
|
||||
# Add entity to query full system
|
||||
entities.append(ISYNodeQueryButtonEntity(isy, uuid))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ISYNodeQueryButtonEntity(ButtonEntity):
|
||||
"""Representation of a device query button entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, node: Node | ISY, base_unique_id: str) -> None:
|
||||
"""Initialize a query ISY device button entity."""
|
||||
self._node = node
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_name = "Query"
|
||||
self._attr_unique_id = f"{base_unique_id}_query"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(ISY994_DOMAIN, base_unique_id)}
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
self.hass.async_create_task(self._node.query())
|
@ -76,6 +76,7 @@ KEY_STATUS = "status"
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CLIMATE,
|
||||
Platform.COVER,
|
||||
Platform.FAN,
|
||||
@ -189,6 +190,14 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = {
|
||||
], # Does a startswith() match; include the dot
|
||||
FILTER_ZWAVE_CAT: (["104", "112", "138"] + list(map(str, range(148, 180)))),
|
||||
},
|
||||
Platform.BUTTON: {
|
||||
# No devices automatically sorted as buttons at this time. Query buttons added elsewhere.
|
||||
FILTER_UOM: [],
|
||||
FILTER_STATES: [],
|
||||
FILTER_NODE_DEF_ID: [],
|
||||
FILTER_INSTEON_TYPE: [],
|
||||
FILTER_ZWAVE_CAT: [],
|
||||
},
|
||||
Platform.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)
|
||||
|
@ -16,12 +16,6 @@ from pyisy.nodes import Group, Node, Nodes
|
||||
from pyisy.programs import Programs
|
||||
from pyisy.variables import Variables
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE
|
||||
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.components.switch import DOMAIN as SWITCH
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -95,7 +89,7 @@ def _check_for_insteon_type(
|
||||
works for Insteon device. "Node Server" (v5+) and Z-Wave and others will
|
||||
not have a type.
|
||||
"""
|
||||
if not hasattr(node, "protocol") or node.protocol != PROTO_INSTEON:
|
||||
if node.protocol != PROTO_INSTEON:
|
||||
return False
|
||||
if not hasattr(node, "type") or node.type is None:
|
||||
# Node doesn't have a type (non-Insteon device most likely)
|
||||
@ -115,34 +109,34 @@ def _check_for_insteon_type(
|
||||
subnode_id = int(node.address.split(" ")[-1], 16)
|
||||
|
||||
# FanLinc, which has a light module as one of its nodes.
|
||||
if platform == FAN and subnode_id == SUBNODE_FANLINC_LIGHT:
|
||||
hass_isy_data[ISY994_NODES][LIGHT].append(node)
|
||||
if platform == Platform.FAN and subnode_id == SUBNODE_FANLINC_LIGHT:
|
||||
hass_isy_data[ISY994_NODES][Platform.LIGHT].append(node)
|
||||
return True
|
||||
|
||||
# Thermostats, which has a "Heat" and "Cool" sub-node on address 2 and 3
|
||||
if platform == CLIMATE and subnode_id in (
|
||||
if platform == Platform.CLIMATE and subnode_id in (
|
||||
SUBNODE_CLIMATE_COOL,
|
||||
SUBNODE_CLIMATE_HEAT,
|
||||
):
|
||||
hass_isy_data[ISY994_NODES][BINARY_SENSOR].append(node)
|
||||
hass_isy_data[ISY994_NODES][Platform.BINARY_SENSOR].append(node)
|
||||
return True
|
||||
|
||||
# IOLincs which have a sensor and relay on 2 different nodes
|
||||
if (
|
||||
platform == BINARY_SENSOR
|
||||
platform == Platform.BINARY_SENSOR
|
||||
and device_type.startswith(TYPE_CATEGORY_SENSOR_ACTUATORS)
|
||||
and subnode_id == SUBNODE_IOLINC_RELAY
|
||||
):
|
||||
hass_isy_data[ISY994_NODES][SWITCH].append(node)
|
||||
hass_isy_data[ISY994_NODES][Platform.SWITCH].append(node)
|
||||
return True
|
||||
|
||||
# Smartenit EZIO2X4
|
||||
if (
|
||||
platform == SWITCH
|
||||
platform == Platform.SWITCH
|
||||
and device_type.startswith(TYPE_EZIO2X4)
|
||||
and subnode_id in SUBNODE_EZIO2X4_SENSORS
|
||||
):
|
||||
hass_isy_data[ISY994_NODES][BINARY_SENSOR].append(node)
|
||||
hass_isy_data[ISY994_NODES][Platform.BINARY_SENSOR].append(node)
|
||||
return True
|
||||
|
||||
hass_isy_data[ISY994_NODES][platform].append(node)
|
||||
@ -159,7 +153,7 @@ def _check_for_zwave_cat(
|
||||
This is for (presumably) every version of the ISY firmware, but only
|
||||
works for Z-Wave Devices with the devtype.cat property.
|
||||
"""
|
||||
if not hasattr(node, "protocol") or node.protocol != PROTO_ZWAVE:
|
||||
if node.protocol != PROTO_ZWAVE:
|
||||
return False
|
||||
|
||||
if not hasattr(node, "zwave_props") or node.zwave_props is None:
|
||||
@ -292,11 +286,15 @@ def _categorize_nodes(
|
||||
# Don't import this node as a device at all
|
||||
continue
|
||||
|
||||
if hasattr(node, "protocol") and node.protocol == PROTO_GROUP:
|
||||
if hasattr(node, "parent_node") and node.parent_node is None:
|
||||
# This is a physical device / parent node, add a query button
|
||||
hass_isy_data[ISY994_NODES][Platform.BUTTON].append(node)
|
||||
|
||||
if node.protocol == PROTO_GROUP:
|
||||
hass_isy_data[ISY994_NODES][ISY_GROUP_PLATFORM].append(node)
|
||||
continue
|
||||
|
||||
if getattr(node, "protocol", None) == PROTO_INSTEON:
|
||||
if node.protocol == PROTO_INSTEON:
|
||||
for control in node.aux_properties:
|
||||
hass_isy_data[ISY994_NODES][SENSOR_AUX].append((node, control))
|
||||
|
||||
@ -305,7 +303,7 @@ def _categorize_nodes(
|
||||
# determine if it should be a binary_sensor.
|
||||
if _is_sensor_a_binary_sensor(hass_isy_data, node):
|
||||
continue
|
||||
hass_isy_data[ISY994_NODES][SENSOR].append(node)
|
||||
hass_isy_data[ISY994_NODES][Platform.SENSOR].append(node)
|
||||
continue
|
||||
|
||||
# We have a bunch of different methods for determining the device type,
|
||||
@ -323,7 +321,7 @@ def _categorize_nodes(
|
||||
continue
|
||||
|
||||
# Fallback as as sensor, e.g. for un-sortable items like NodeServer nodes.
|
||||
hass_isy_data[ISY994_NODES][SENSOR].append(node)
|
||||
hass_isy_data[ISY994_NODES][Platform.SENSOR].append(node)
|
||||
|
||||
|
||||
def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None:
|
||||
@ -348,7 +346,7 @@ def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None:
|
||||
)
|
||||
continue
|
||||
|
||||
if platform != BINARY_SENSOR:
|
||||
if platform != Platform.BINARY_SENSOR:
|
||||
actions = entity_folder.get_by_name(KEY_ACTIONS)
|
||||
if not actions or actions.protocol != PROTO_PROGRAM:
|
||||
_LOGGER.warning(
|
||||
|
@ -13,12 +13,14 @@ from homeassistant.const import (
|
||||
CONF_TYPE,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
SERVICE_RELOAD,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.helpers import entity_platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import async_get_platforms
|
||||
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
|
||||
@ -181,7 +183,7 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
"""Handle a system query service call."""
|
||||
address = service.data.get(CONF_ADDRESS)
|
||||
isy_name = service.data.get(CONF_ISY)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
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"]:
|
||||
@ -195,11 +197,31 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
isy.configuration["uuid"],
|
||||
)
|
||||
await isy.query(address)
|
||||
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']}_{address}_query",
|
||||
),
|
||||
breaks_in_ha_version="2023.5.0",
|
||||
)
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"Requesting system query of ISY %s", isy.configuration["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"
|
||||
),
|
||||
breaks_in_ha_version="2023.5.0",
|
||||
)
|
||||
|
||||
async def async_run_network_resource_service_handler(service: ServiceCall) -> None:
|
||||
"""Handle a network resource service call."""
|
||||
@ -447,3 +469,43 @@ def async_setup_light_services(hass: HomeAssistant) -> None:
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_RAMP_RATE, SERVICE_SET_RAMP_RATE_SCHEMA, "async_set_ramp_rate"
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_log_deprecated_service_call(
|
||||
hass: HomeAssistant,
|
||||
call: ServiceCall,
|
||||
alternate_service: str,
|
||||
alternate_target: str | None,
|
||||
breaks_in_ha_version: str,
|
||||
) -> None:
|
||||
"""Log a warning about a deprecated service call."""
|
||||
deprecated_service = f"{call.domain}.{call.service}"
|
||||
alternate_target = alternate_target or "this device"
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_service_{deprecated_service}",
|
||||
breaks_in_ha_version=breaks_in_ha_version,
|
||||
is_fixable=True,
|
||||
is_persistent=True,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_service",
|
||||
translation_placeholders={
|
||||
"alternate_service": alternate_service,
|
||||
"alternate_target": alternate_target,
|
||||
"deprecated_service": deprecated_service,
|
||||
},
|
||||
)
|
||||
|
||||
_LOGGER.warning(
|
||||
(
|
||||
'The "%s" service is deprecated and will be removed in %s; use the "%s" '
|
||||
'service and pass it a target entity ID of "%s"'
|
||||
),
|
||||
deprecated_service,
|
||||
breaks_in_ha_version,
|
||||
alternate_service,
|
||||
alternate_target,
|
||||
)
|
||||
|
@ -168,8 +168,8 @@ set_ramp_rate:
|
||||
min: 0
|
||||
max: 31
|
||||
system_query:
|
||||
name: System query
|
||||
description: Request the ISY Query the connected devices.
|
||||
name: System query (Deprecated)
|
||||
description: "Request the ISY Query the connected devices. Deprecated: Use device Query button entity."
|
||||
fields:
|
||||
address:
|
||||
name: Address
|
||||
|
@ -53,5 +53,18 @@
|
||||
"last_heartbeat": "Last Heartbeat Time",
|
||||
"websocket_status": "Event Socket Status"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_service": {
|
||||
"title": "The {deprecated_service} service will be removed",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "The {deprecated_service} service will be removed",
|
||||
"description": "Update any automations or scripts that use this service to instead use the `{alternate_service}` service with a target entity ID of `{alternate_target}`."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,5 +53,18 @@
|
||||
"last_heartbeat": "Last Heartbeat Time",
|
||||
"websocket_status": "Event Socket Status"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_service": {
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Update any automations or scripts that use this service to instead use the `{alternate_service}` service with a target entity ID of `{alternate_target}`.",
|
||||
"title": "The {deprecated_service} service will be removed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "The {deprecated_service} service will be removed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user