Remove deprecated support for lock sensors and corresponding actions in lcn (#147143)

This commit is contained in:
Andre Lengwenus 2025-07-04 22:55:20 +02:00 committed by GitHub
parent 520d92b902
commit 8f24ebe967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 10 additions and 373 deletions

View File

@ -5,23 +5,16 @@ from functools import partial
import pypck
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.binary_sensor import (
DOMAIN as DOMAIN_BINARY_SENSOR,
BinarySensorEntity,
)
from homeassistant.components.script import scripts_with_entity
from homeassistant.const import CONF_DOMAIN, CONF_ENTITIES, CONF_SOURCE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from homeassistant.helpers.typing import ConfigType
from .const import BINSENSOR_PORTS, CONF_DOMAIN_DATA, DOMAIN, SETPOINTS
from .const import CONF_DOMAIN_DATA
from .entity import LcnEntity
from .helpers import InputType, LcnConfigEntry
@ -34,15 +27,9 @@ def add_lcn_entities(
entity_configs: Iterable[ConfigType],
) -> None:
"""Add entities for this domain."""
entities: list[LcnRegulatorLockSensor | LcnBinarySensor | LcnLockKeysSensor] = []
for entity_config in entity_configs:
if entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in SETPOINTS:
entities.append(LcnRegulatorLockSensor(entity_config, config_entry))
elif entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in BINSENSOR_PORTS:
entities.append(LcnBinarySensor(entity_config, config_entry))
else: # in KEY
entities.append(LcnLockKeysSensor(entity_config, config_entry))
entities = [
LcnBinarySensor(entity_config, config_entry) for entity_config in entity_configs
]
async_add_entities(entities)
@ -71,65 +58,6 @@ async def async_setup_entry(
)
class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity):
"""Representation of a LCN binary sensor for regulator locks."""
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
"""Initialize the LCN binary sensor."""
super().__init__(config, config_entry)
self.setpoint_variable = pypck.lcn_defs.Var[
config[CONF_DOMAIN_DATA][CONF_SOURCE]
]
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
if not self.device_connection.is_group:
await self.device_connection.activate_status_request_handler(
self.setpoint_variable
)
entity_automations = automations_with_entity(self.hass, self.entity_id)
entity_scripts = scripts_with_entity(self.hass, self.entity_id)
if entity_automations + entity_scripts:
async_create_issue(
self.hass,
DOMAIN,
f"deprecated_binary_sensor_{self.entity_id}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_regulatorlock_sensor",
translation_placeholders={
"entity": f"{DOMAIN_BINARY_SENSOR}.{self.name.lower().replace(' ', '_')}",
},
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
await super().async_will_remove_from_hass()
if not self.device_connection.is_group:
await self.device_connection.cancel_status_request_handler(
self.setpoint_variable
)
async_delete_issue(
self.hass, DOMAIN, f"deprecated_binary_sensor_{self.entity_id}"
)
def input_received(self, input_obj: InputType) -> None:
"""Set sensor value when LCN input object (command) is received."""
if (
not isinstance(input_obj, pypck.inputs.ModStatusVar)
or input_obj.get_var() != self.setpoint_variable
):
return
self._attr_is_on = input_obj.get_value().is_locked_regulator()
self.async_write_ha_state()
class LcnBinarySensor(LcnEntity, BinarySensorEntity):
"""Representation of a LCN binary sensor for binary sensor ports."""
@ -164,59 +92,3 @@ class LcnBinarySensor(LcnEntity, BinarySensorEntity):
self._attr_is_on = input_obj.get_state(self.bin_sensor_port.value)
self.async_write_ha_state()
class LcnLockKeysSensor(LcnEntity, BinarySensorEntity):
"""Representation of a LCN sensor for key locks."""
def __init__(self, config: ConfigType, config_entry: LcnConfigEntry) -> None:
"""Initialize the LCN sensor."""
super().__init__(config, config_entry)
self.source = pypck.lcn_defs.Key[config[CONF_DOMAIN_DATA][CONF_SOURCE]]
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
if not self.device_connection.is_group:
await self.device_connection.activate_status_request_handler(self.source)
entity_automations = automations_with_entity(self.hass, self.entity_id)
entity_scripts = scripts_with_entity(self.hass, self.entity_id)
if entity_automations + entity_scripts:
async_create_issue(
self.hass,
DOMAIN,
f"deprecated_binary_sensor_{self.entity_id}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_keylock_sensor",
translation_placeholders={
"entity": f"{DOMAIN_BINARY_SENSOR}.{self.name.lower().replace(' ', '_')}",
},
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
await super().async_will_remove_from_hass()
if not self.device_connection.is_group:
await self.device_connection.cancel_status_request_handler(self.source)
async_delete_issue(
self.hass, DOMAIN, f"deprecated_binary_sensor_{self.entity_id}"
)
def input_received(self, input_obj: InputType) -> None:
"""Set sensor value when LCN input object (command) is received."""
if (
not isinstance(input_obj, pypck.inputs.ModStatusKeyLocks)
or self.source not in pypck.lcn_defs.Key
):
return
table_id = ord(self.source.name[0]) - 65
key_id = int(self.source.name[1]) - 1
self._attr_is_on = input_obj.get_state(table_id, key_id)
self.async_write_ha_state()

View File

@ -9,5 +9,5 @@
"iot_class": "local_push",
"loggers": ["pypck"],
"quality_scale": "bronze",
"requirements": ["pypck==0.8.10", "lcn-frontend==0.2.5"]
"requirements": ["pypck==0.8.10", "lcn-frontend==0.2.6"]
}

2
requirements_all.txt generated
View File

@ -1319,7 +1319,7 @@ lakeside==0.13
laundrify-aio==1.2.2
# homeassistant.components.lcn
lcn-frontend==0.2.5
lcn-frontend==0.2.6
# homeassistant.components.ld2410_ble
ld2410-ble==0.1.1

View File

@ -1138,7 +1138,7 @@ lacrosse-view==1.1.1
laundrify-aio==1.2.2
# homeassistant.components.lcn
lcn-frontend==0.2.5
lcn-frontend==0.2.6
# homeassistant.components.ld2410_ble
ld2410-ble==0.1.1

View File

@ -187,14 +187,6 @@
"transition": 10.0
}
},
{
"address": [0, 7, false],
"name": "Sensor_LockRegulator1",
"domain": "binary_sensor",
"domain_data": {
"source": "R1VARSETPOINT"
}
},
{
"address": [0, 7, false],
"name": "Binary_Sensor1",
@ -203,14 +195,6 @@
"source": "BINSENSOR1"
}
},
{
"address": [0, 7, false],
"name": "Sensor_KeyLock",
"domain": "binary_sensor",
"domain_data": {
"source": "A5"
}
},
{
"address": [0, 7, false],
"name": "Sensor_Var1",

View File

@ -47,99 +47,3 @@
'state': 'unknown',
})
# ---
# name: test_setup_lcn_binary_sensor[binary_sensor.testmodule_sensor_keylock-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.testmodule_sensor_keylock',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Sensor_KeyLock',
'platform': 'lcn',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'lcn/config_entry_pchk_json-m000007-a5',
'unit_of_measurement': None,
})
# ---
# name: test_setup_lcn_binary_sensor[binary_sensor.testmodule_sensor_keylock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TestModule Sensor_KeyLock',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.testmodule_sensor_keylock',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_setup_lcn_binary_sensor[binary_sensor.testmodule_sensor_lockregulator1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.testmodule_sensor_lockregulator1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Sensor_LockRegulator1',
'platform': 'lcn',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'lcn/config_entry_pchk_json-m000007-r1varsetpoint',
'unit_of_measurement': None,
})
# ---
# name: test_setup_lcn_binary_sensor[binary_sensor.testmodule_sensor_lockregulator1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'TestModule Sensor_LockRegulator1',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.testmodule_sensor_lockregulator1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@ -2,29 +2,20 @@
from unittest.mock import patch
from pypck.inputs import ModStatusBinSensors, ModStatusKeyLocks, ModStatusVar
from pypck.inputs import ModStatusBinSensors
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import Var, VarValue
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import automation, script
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.lcn import DOMAIN
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.components.script import scripts_with_entity
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.setup import async_setup_component
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, init_integration
from tests.common import snapshot_platform
BINARY_SENSOR_LOCKREGULATOR1 = "binary_sensor.testmodule_sensor_lockregulator1"
BINARY_SENSOR_SENSOR1 = "binary_sensor.testmodule_binary_sensor1"
BINARY_SENSOR_KEYLOCK = "binary_sensor.testmodule_sensor_keylock"
async def test_setup_lcn_binary_sensor(
@ -40,35 +31,6 @@ async def test_setup_lcn_binary_sensor(
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
async def test_pushed_lock_setpoint_status_change(
hass: HomeAssistant,
entry: MockConfigEntry,
) -> None:
"""Test the lock setpoint sensor changes its state on status received."""
await init_integration(hass, entry)
device_connection = get_device_connection(hass, (0, 7, False), entry)
address = LcnAddr(0, 7, False)
# push status lock setpoint
inp = ModStatusVar(address, Var.R1VARSETPOINT, VarValue(0x8000))
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(BINARY_SENSOR_LOCKREGULATOR1)
assert state is not None
assert state.state == STATE_ON
# push status unlock setpoint
inp = ModStatusVar(address, Var.R1VARSETPOINT, VarValue(0x7FFF))
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(BINARY_SENSOR_LOCKREGULATOR1)
assert state is not None
assert state.state == STATE_OFF
async def test_pushed_binsensor_status_change(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
@ -99,94 +61,9 @@ async def test_pushed_binsensor_status_change(
assert state.state == STATE_ON
async def test_pushed_keylock_status_change(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test the keylock sensor changes its state on status received."""
await init_integration(hass, entry)
device_connection = get_device_connection(hass, (0, 7, False), entry)
address = LcnAddr(0, 7, False)
states = [[False] * 8 for i in range(4)]
# push status keylock "off"
inp = ModStatusKeyLocks(address, states)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(BINARY_SENSOR_KEYLOCK)
assert state is not None
assert state.state == STATE_OFF
# push status keylock "on"
states[0][4] = True
inp = ModStatusKeyLocks(address, states)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(BINARY_SENSOR_KEYLOCK)
assert state is not None
assert state.state == STATE_ON
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the binary sensor is removed when the config entry is unloaded."""
await init_integration(hass, entry)
await hass.config_entries.async_unload(entry.entry_id)
assert hass.states.get(BINARY_SENSOR_LOCKREGULATOR1).state == STATE_UNAVAILABLE
assert hass.states.get(BINARY_SENSOR_SENSOR1).state == STATE_UNAVAILABLE
assert hass.states.get(BINARY_SENSOR_KEYLOCK).state == STATE_UNAVAILABLE
@pytest.mark.parametrize(
"entity_id",
[
"binary_sensor.testmodule_sensor_lockregulator1",
"binary_sensor.testmodule_sensor_keylock",
],
)
async def test_create_issue(
hass: HomeAssistant,
service_calls: list[ServiceCall],
issue_registry: ir.IssueRegistry,
entry: MockConfigEntry,
entity_id,
) -> None:
"""Test we create an issue when an automation or script is using a deprecated entity."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"alias": "test",
"trigger": {"platform": "state", "entity_id": entity_id},
"action": {"action": "test.automation"},
}
},
)
assert await async_setup_component(
hass,
script.DOMAIN,
{
script.DOMAIN: {
"test": {
"sequence": {
"condition": "state",
"entity_id": entity_id,
"state": STATE_ON,
}
}
}
},
)
await init_integration(hass, entry)
assert automations_with_entity(hass, entity_id)[0] == "automation.test"
assert scripts_with_entity(hass, entity_id)[0] == "script.test"
assert issue_registry.async_get_issue(
DOMAIN, f"deprecated_binary_sensor_{entity_id}"
)