1
0
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:
AlCalzone
2026-03-26 11:45:34 +01:00
committed by GitHub
parent b6c2fbb8c0
commit d39ef523b8
3 changed files with 7 additions and 352 deletions

View File

@@ -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":

View File

@@ -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": {

View File

@@ -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
)