mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 08:26:41 +01:00
Revert: Create repair issue for legacy Z-Wave Door state sensors that are still in use (#166583)
This commit is contained in:
@@ -18,27 +18,17 @@ from zwave_js_server.const.command_class.notification import (
|
||||
)
|
||||
from zwave_js_server.model.driver import Driver
|
||||
|
||||
from homeassistant.components.automation import automations_with_entity
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import NewZwaveDiscoveryInfo, ZWaveBaseEntity
|
||||
@@ -413,93 +403,6 @@ def is_valid_notification_binary_sensor(
|
||||
return len(info.primary_value.metadata.states) > 1
|
||||
|
||||
|
||||
@callback
|
||||
def _async_check_legacy_entity_repair(
|
||||
hass: HomeAssistant,
|
||||
driver: Driver,
|
||||
entity: ZWaveLegacyDoorStateBinarySensor,
|
||||
) -> None:
|
||||
"""Schedule a repair issue check once HA has fully started."""
|
||||
|
||||
@callback
|
||||
def _async_do_check(hass: HomeAssistant) -> None:
|
||||
"""Create or delete a repair issue for a deprecated legacy door state entity."""
|
||||
ent_reg = er.async_get(hass)
|
||||
if entity.unique_id is None:
|
||||
return
|
||||
entity_id = ent_reg.async_get_entity_id(
|
||||
BINARY_SENSOR_DOMAIN, DOMAIN, entity.unique_id
|
||||
)
|
||||
if entity_id is None:
|
||||
return
|
||||
|
||||
issue_id = f"deprecated_legacy_door_state.{entity_id}"
|
||||
|
||||
# Delete any stale repair issue if the entity is disabled or missing —
|
||||
# the user has already dealt with it.
|
||||
entity_entry = ent_reg.async_get(entity_id)
|
||||
if entity_entry is None or entity_entry.disabled:
|
||||
async_delete_issue(hass, DOMAIN, issue_id)
|
||||
return
|
||||
|
||||
entity_automations = automations_with_entity(hass, entity_id)
|
||||
entity_scripts = scripts_with_entity(hass, entity_id)
|
||||
|
||||
# Delete any stale repair issue if the entity is no longer referenced
|
||||
# in any automation or script.
|
||||
if not entity_automations and not entity_scripts:
|
||||
async_delete_issue(hass, DOMAIN, issue_id)
|
||||
return
|
||||
|
||||
opening_state_value = get_opening_state_notification_value(
|
||||
entity.info.node, entity.info.primary_value.endpoint
|
||||
)
|
||||
if opening_state_value is None:
|
||||
async_delete_issue(hass, DOMAIN, issue_id)
|
||||
return
|
||||
opening_state_unique_id = (
|
||||
f"{driver.controller.home_id}.{opening_state_value.value_id}"
|
||||
)
|
||||
opening_state_entity_id = ent_reg.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, opening_state_unique_id
|
||||
)
|
||||
# Delete any stale repair issue if the replacement opening state sensor
|
||||
# no longer exists for some reason
|
||||
if opening_state_entity_id is None:
|
||||
async_delete_issue(hass, DOMAIN, issue_id)
|
||||
return
|
||||
|
||||
items = [
|
||||
f"- [{item.name or item.original_name or eid}](/config/{domain}/edit/{item.unique_id})"
|
||||
for domain, entity_ids in (
|
||||
("automation", entity_automations),
|
||||
("script", entity_scripts),
|
||||
)
|
||||
for eid in entity_ids
|
||||
if (item := ent_reg.async_get(eid))
|
||||
]
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
issue_id,
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_legacy_door_state",
|
||||
translation_placeholders={
|
||||
"entity_id": entity_id,
|
||||
"entity_name": entity_entry.name
|
||||
or entity_entry.original_name
|
||||
or entity_id,
|
||||
"opening_state_entity_id": opening_state_entity_id,
|
||||
"items": "\n".join(items),
|
||||
},
|
||||
)
|
||||
|
||||
async_at_started(hass, _async_do_check)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ZwaveJSConfigEntry,
|
||||
@@ -543,9 +446,9 @@ async def async_setup_entry(
|
||||
isinstance(info, NewZwaveDiscoveryInfo)
|
||||
and info.entity_class is ZWaveLegacyDoorStateBinarySensor
|
||||
):
|
||||
entity = ZWaveLegacyDoorStateBinarySensor(config_entry, driver, info)
|
||||
entities.append(entity)
|
||||
_async_check_legacy_entity_repair(hass, driver, entity)
|
||||
entities.append(
|
||||
ZWaveLegacyDoorStateBinarySensor(config_entry, driver, info)
|
||||
)
|
||||
elif isinstance(info, NewZwaveDiscoveryInfo):
|
||||
pass # other entity classes are not migrated yet
|
||||
elif info.platform_hint == "notification":
|
||||
|
||||
@@ -303,10 +303,6 @@
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_legacy_door_state": {
|
||||
"description": "The binary sensor `{entity_id}` is deprecated because it has been replaced with the opening state sensor `{opening_state_entity_id}`.\n\nThe entity was found in the following automations or scripts:\n{items}\n\nPlease update the above automations or scripts to use the opening state sensor `{opening_state_entity_id}` and disable the binary sensor `{entity_id}` to fix this issue.\n\nNote that `{opening_state_entity_id}` reports three states:\n- Closed\n- Open\n- Tilted (if supported by the device).",
|
||||
"title": "Deprecation: {entity_name}"
|
||||
},
|
||||
"device_config_file_changed": {
|
||||
"fix_flow": {
|
||||
"abort": {
|
||||
|
||||
@@ -7,14 +7,9 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from zwave_js_server.event import Event
|
||||
from zwave_js_server.model.node import Node, NodeDataType
|
||||
from zwave_js_server.model.node import Node
|
||||
|
||||
from homeassistant.components import automation
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.zwave_js.const import DOMAIN
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
@@ -25,9 +20,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .common import (
|
||||
@@ -36,6 +29,7 @@ from .common import (
|
||||
NOTIFICATION_MOTION_BINARY_SENSOR,
|
||||
PROPERTY_DOOR_STATUS_BINARY_SENSOR,
|
||||
TAMPER_SENSOR,
|
||||
NodeDataType,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
@@ -989,241 +983,3 @@ async def test_hoppe_ehandle_connectsense(
|
||||
assert entry.original_name == "Window/door is tilted"
|
||||
assert entry.original_device_class == BinarySensorDeviceClass.WINDOW
|
||||
assert entry.disabled_by is None, "Entity should be enabled by default"
|
||||
|
||||
|
||||
async def test_legacy_door_state_repair_issue(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
client: MagicMock,
|
||||
hoppe_ehandle_connectsense_state: NodeDataType,
|
||||
) -> None:
|
||||
"""Test repair issue is created only when legacy door state entity is in automation."""
|
||||
node = Node(client, hoppe_ehandle_connectsense_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
home_id = client.driver.controller.home_id
|
||||
|
||||
# Pre-register the legacy entity as enabled (simulating existing user entity).
|
||||
unique_id = f"{home_id}.20-113-0-Access Control-Door state.22"
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
unique_id,
|
||||
suggested_object_id="ehandle_connectsense_window_door_is_open",
|
||||
original_name="Window/door is open",
|
||||
)
|
||||
entity_id = entity_entry.entity_id
|
||||
|
||||
# Load the integration without any automation referencing the entity.
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No repair issues should exist without automations.
|
||||
issues = [
|
||||
issue
|
||||
for issue in issue_registry.issues.values()
|
||||
if issue.domain == DOMAIN
|
||||
and issue.translation_key == "deprecated_legacy_door_state"
|
||||
]
|
||||
assert len(issues) == 0
|
||||
|
||||
# Now set up an automation referencing the legacy entity.
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"id": "test_automation",
|
||||
"alias": "test",
|
||||
"trigger": {"platform": "state", "entity_id": entity_id},
|
||||
"action": {
|
||||
"action": "automation.turn_on",
|
||||
"target": {"entity_id": "automation.test_automation"},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Reload the integration so the repair check runs again.
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"deprecated_legacy_door_state.{entity_id}"
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.translation_key == "deprecated_legacy_door_state"
|
||||
assert issue.translation_placeholders["entity_id"] == entity_id
|
||||
assert issue.translation_placeholders["entity_name"] == "Window/door is open"
|
||||
assert (
|
||||
issue.translation_placeholders["opening_state_entity_id"]
|
||||
== "sensor.ehandle_connectsense_opening_state"
|
||||
)
|
||||
assert "test" in issue.translation_placeholders["items"]
|
||||
|
||||
|
||||
async def test_legacy_door_state_no_repair_issue_when_disabled(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
client: MagicMock,
|
||||
hoppe_ehandle_connectsense_state: NodeDataType,
|
||||
) -> None:
|
||||
"""Test no repair issue when legacy door state entity is disabled."""
|
||||
node = Node(client, hoppe_ehandle_connectsense_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
home_id = client.driver.controller.home_id
|
||||
|
||||
# Pre-register the legacy entity as disabled.
|
||||
unique_id = f"{home_id}.20-113-0-Access Control-Door state.22"
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
unique_id,
|
||||
suggested_object_id="ehandle_connectsense_window_door_is_open",
|
||||
original_name="Window/door is open",
|
||||
disabled_by=er.RegistryEntryDisabler.INTEGRATION,
|
||||
)
|
||||
entity_id = entity_entry.entity_id
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"id": "test_automation",
|
||||
"alias": "test",
|
||||
"trigger": {"platform": "state", "entity_id": entity_id},
|
||||
"action": {
|
||||
"action": "automation.turn_on",
|
||||
"target": {"entity_id": "automation.test_automation"},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No repair issue should be created since the entity is disabled.
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"deprecated_legacy_door_state.{entity_id}"
|
||||
)
|
||||
assert issue is None
|
||||
|
||||
|
||||
async def test_hoppe_custom_tilt_sensor_no_repair_issue(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
client: MagicMock,
|
||||
hoppe_ehandle_connectsense_state: NodeDataType,
|
||||
) -> None:
|
||||
"""Test no repair issue for Hoppe eHandle custom tilt sensor (Binary Sensor CC)."""
|
||||
node = Node(client, hoppe_ehandle_connectsense_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
|
||||
# Pre-register the Hoppe tilt entity as enabled (simulating existing user entity).
|
||||
home_id = client.driver.controller.home_id
|
||||
unique_id = f"{home_id}.20-48-0-Tilt"
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
unique_id,
|
||||
suggested_object_id="ehandle_connectsense_window_door_is_tilted",
|
||||
original_name="Window/door is tilted",
|
||||
)
|
||||
entity_id = entity_entry.entity_id
|
||||
|
||||
# Set up automation referencing the custom tilt entity.
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"id": "test_automation",
|
||||
"alias": "test",
|
||||
"trigger": {"platform": "state", "entity_id": entity_id},
|
||||
"action": {
|
||||
"action": "automation.turn_on",
|
||||
"target": {"entity_id": "automation.test_automation"},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No repair issue should be created - this is a custom Binary Sensor CC entity,
|
||||
# not a legacy Notification CC door state entity.
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"deprecated_legacy_door_state.{entity_id}"
|
||||
)
|
||||
assert issue is None
|
||||
|
||||
|
||||
async def test_legacy_door_state_stale_repair_issue_cleaned_up(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
client: MagicMock,
|
||||
hoppe_ehandle_connectsense_state: NodeDataType,
|
||||
) -> None:
|
||||
"""Test that a stale repair issue is deleted when there are no automations."""
|
||||
node = Node(client, hoppe_ehandle_connectsense_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
home_id = client.driver.controller.home_id
|
||||
|
||||
# Pre-register the legacy entity as enabled.
|
||||
unique_id = f"{home_id}.20-113-0-Access Control-Door state.22"
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
BINARY_SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
unique_id,
|
||||
suggested_object_id="ehandle_connectsense_window_door_is_open",
|
||||
original_name="Window/door is open",
|
||||
)
|
||||
entity_id = entity_entry.entity_id
|
||||
|
||||
# Seed a stale repair issue as if it had been created in a previous run.
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_legacy_door_state.{entity_id}",
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_legacy_door_state",
|
||||
translation_placeholders={
|
||||
"entity_id": entity_id,
|
||||
"entity_name": "Window/door is open",
|
||||
"opening_state_entity_id": "sensor.ehandle_connectsense_opening_state",
|
||||
"items": "- [test](/config/automation/edit/test_automation)",
|
||||
},
|
||||
)
|
||||
assert (
|
||||
issue_registry.async_get_issue(
|
||||
DOMAIN, f"deprecated_legacy_door_state.{entity_id}"
|
||||
)
|
||||
is not None
|
||||
)
|
||||
|
||||
# Load the integration with no automation referencing the legacy entity.
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Stale issue should have been cleaned up.
|
||||
assert (
|
||||
issue_registry.async_get_issue(
|
||||
DOMAIN, f"deprecated_legacy_door_state.{entity_id}"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user