Remove deprecated address argument in all lcn services (#144557)

This commit is contained in:
Robert Resch 2025-05-09 15:15:09 +02:00 committed by GitHub
parent 9e3684b001
commit 763f2bcfcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 167 deletions

View File

@ -283,26 +283,6 @@ def get_device_config(
return None
def is_address(value: str) -> tuple[AddressType, str]:
"""Validate the given address string.
Examples for S000M005 at myhome:
myhome.s000.m005
myhome.s0.m5
myhome.0.5 ("m" is implicit if missing)
Examples for s000g011
myhome.0.g11
myhome.s0.g11
"""
if matcher := PATTERN_ADDRESS.match(value):
is_group = matcher.group("type") == "g"
addr = (int(matcher.group("seg_id")), int(matcher.group("id")), is_group)
conn_id = matcher.group("conn_id")
return addr, conn_id
raise ValueError(f"{value} is not a valid address string")
def is_states_string(states_string: str) -> list[str]:
"""Validate the given states string and return states list."""
if len(states_string) != 8:

View File

@ -6,10 +6,8 @@ import pypck
import voluptuous as vol
from homeassistant.const import (
CONF_ADDRESS,
CONF_BRIGHTNESS,
CONF_DEVICE_ID,
CONF_HOST,
CONF_STATE,
CONF_UNIT_OF_MEASUREMENT,
)
@ -21,7 +19,6 @@ from homeassistant.core import (
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .const import (
CONF_KEYS,
@ -51,12 +48,7 @@ from .const import (
VAR_UNITS,
VARIABLES,
)
from .helpers import (
DeviceConnectionType,
get_device_connection,
is_address,
is_states_string,
)
from .helpers import DeviceConnectionType, is_states_string
class LcnServiceCall:
@ -64,8 +56,7 @@ class LcnServiceCall:
schema = vol.Schema(
{
vol.Optional(CONF_DEVICE_ID): cv.string,
vol.Optional(CONF_ADDRESS): is_address,
vol.Required(CONF_DEVICE_ID): cv.string,
}
)
supports_response = SupportsResponse.NONE
@ -76,46 +67,18 @@ class LcnServiceCall:
def get_device_connection(self, service: ServiceCall) -> DeviceConnectionType:
"""Get address connection object."""
if CONF_DEVICE_ID not in service.data and CONF_ADDRESS not in service.data:
device_id = service.data[CONF_DEVICE_ID]
device_registry = dr.async_get(self.hass)
if not (device := device_registry.async_get(device_id)):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="no_device_identifier",
translation_key="invalid_device_id",
translation_placeholders={"device_id": device_id},
)
if CONF_DEVICE_ID in service.data:
device_id = service.data[CONF_DEVICE_ID]
device_registry = dr.async_get(self.hass)
if not (device := device_registry.async_get(device_id)):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_device_id",
translation_placeholders={"device_id": device_id},
)
return self.hass.data[DOMAIN][device.primary_config_entry][
DEVICE_CONNECTIONS
][device_id]
async_create_issue(
self.hass,
DOMAIN,
"deprecated_address_parameter",
breaks_in_ha_version="2025.6.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_address_parameter",
)
address, host_name = service.data[CONF_ADDRESS]
for config_entry in self.hass.config_entries.async_entries(DOMAIN):
if config_entry.data[CONF_HOST] == host_name:
device_connection = get_device_connection(
self.hass, address, config_entry
)
if device_connection is None:
raise ValueError("Wrong address.")
return device_connection
raise ValueError("Invalid host name.")
return self.hass.data[DOMAIN][device.primary_config_entry][DEVICE_CONNECTIONS][
device_id
]
async def async_call_service(self, service: ServiceCall) -> ServiceResponse:
"""Execute service call."""

View File

@ -81,10 +81,6 @@
"deprecated_keylock_sensor": {
"title": "Deprecated LCN key lock binary sensor",
"description": "Your LCN key lock binary sensor entity `{entity}` is being used in automations or scripts. A key lock switch entity is available and should be used going forward.\n\nPlease adjust your automations or scripts to fix this issue."
},
"deprecated_address_parameter": {
"title": "Deprecated 'address' parameter",
"description": "The 'address' parameter in the LCN action calls is deprecated. The 'device ID' parameter should be used going forward.\n\nPlease adjust your automations or scripts to fix this issue."
}
},
"services": {
@ -418,9 +414,6 @@
}
},
"exceptions": {
"no_device_identifier": {
"message": "No device identifier provided. Please provide the device ID."
},
"invalid_address": {
"message": "LCN device for given address has not been configured."
},

View File

@ -24,14 +24,12 @@ from homeassistant.components.lcn.const import (
)
from homeassistant.components.lcn.services import LcnService
from homeassistant.const import (
CONF_ADDRESS,
CONF_BRIGHTNESS,
CONF_DEVICE_ID,
CONF_STATE,
CONF_UNIT_OF_MEASUREMENT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
from .conftest import (
@ -42,20 +40,9 @@ from .conftest import (
)
def device_config(
hass: HomeAssistant, entry: MockConfigEntry, config_type: str
) -> dict[str, str]:
"""Return test device config depending on type."""
if config_type == CONF_ADDRESS:
return {CONF_ADDRESS: "pchk.s0.m7"}
return {CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id}
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_output_abs(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test output_abs service."""
await async_setup_component(hass, "persistent_notification", {})
@ -66,7 +53,7 @@ async def test_service_output_abs(
DOMAIN,
LcnService.OUTPUT_ABS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_OUTPUT: "output1",
CONF_BRIGHTNESS: 100,
CONF_TRANSITION: 5,
@ -77,11 +64,9 @@ async def test_service_output_abs(
dim_output.assert_awaited_with(0, 100, 9)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_output_rel(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test output_rel service."""
await async_setup_component(hass, "persistent_notification", {})
@ -92,7 +77,7 @@ async def test_service_output_rel(
DOMAIN,
LcnService.OUTPUT_REL,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_OUTPUT: "output1",
CONF_BRIGHTNESS: 25,
},
@ -102,11 +87,9 @@ async def test_service_output_rel(
rel_output.assert_awaited_with(0, 25)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_output_toggle(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test output_toggle service."""
await async_setup_component(hass, "persistent_notification", {})
@ -117,7 +100,7 @@ async def test_service_output_toggle(
DOMAIN,
LcnService.OUTPUT_TOGGLE,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_OUTPUT: "output1",
CONF_TRANSITION: 5,
},
@ -127,11 +110,9 @@ async def test_service_output_toggle(
toggle_output.assert_awaited_with(0, 9)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_relays(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test relays service."""
await async_setup_component(hass, "persistent_notification", {})
@ -141,7 +122,10 @@ async def test_service_relays(
await hass.services.async_call(
DOMAIN,
LcnService.RELAYS,
{**device_config(hass, entry, config_type), CONF_STATE: "0011TT--"},
{
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_STATE: "0011TT--",
},
blocking=True,
)
@ -151,11 +135,9 @@ async def test_service_relays(
control_relays.assert_awaited_with(relay_states)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_led(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test led service."""
await async_setup_component(hass, "persistent_notification", {})
@ -166,7 +148,7 @@ async def test_service_led(
DOMAIN,
LcnService.LED,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_LED: "led6",
CONF_STATE: "blink",
},
@ -179,11 +161,9 @@ async def test_service_led(
control_led.assert_awaited_with(led, led_state)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_var_abs(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test var_abs service."""
await async_setup_component(hass, "persistent_notification", {})
@ -194,7 +174,7 @@ async def test_service_var_abs(
DOMAIN,
LcnService.VAR_ABS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_VARIABLE: "var1",
CONF_VALUE: 75,
CONF_UNIT_OF_MEASUREMENT: "%",
@ -207,11 +187,9 @@ async def test_service_var_abs(
)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_var_rel(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test var_rel service."""
await async_setup_component(hass, "persistent_notification", {})
@ -222,7 +200,7 @@ async def test_service_var_rel(
DOMAIN,
LcnService.VAR_REL,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_VARIABLE: "var1",
CONF_VALUE: 10,
CONF_UNIT_OF_MEASUREMENT: "%",
@ -239,11 +217,9 @@ async def test_service_var_rel(
)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_var_reset(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test var_reset service."""
await async_setup_component(hass, "persistent_notification", {})
@ -253,18 +229,19 @@ async def test_service_var_reset(
await hass.services.async_call(
DOMAIN,
LcnService.VAR_RESET,
{**device_config(hass, entry, config_type), CONF_VARIABLE: "var1"},
{
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_VARIABLE: "var1",
},
blocking=True,
)
var_reset.assert_awaited_with(pypck.lcn_defs.Var["VAR1"])
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_lock_regulator(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test lock_regulator service."""
await async_setup_component(hass, "persistent_notification", {})
@ -275,7 +252,7 @@ async def test_service_lock_regulator(
DOMAIN,
LcnService.LOCK_REGULATOR,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_SETPOINT: "r1varsetpoint",
CONF_STATE: True,
},
@ -285,11 +262,9 @@ async def test_service_lock_regulator(
lock_regulator.assert_awaited_with(0, True)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_send_keys(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test send_keys service."""
await async_setup_component(hass, "persistent_notification", {})
@ -300,7 +275,7 @@ async def test_service_send_keys(
DOMAIN,
LcnService.SEND_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_KEYS: "a1a5d8",
CONF_STATE: "hit",
},
@ -315,11 +290,9 @@ async def test_service_send_keys(
send_keys.assert_awaited_with(keys, pypck.lcn_defs.SendKeyCommand["HIT"])
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_send_keys_hit_deferred(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test send_keys (hit_deferred) service."""
await async_setup_component(hass, "persistent_notification", {})
@ -338,7 +311,7 @@ async def test_service_send_keys_hit_deferred(
DOMAIN,
LcnService.SEND_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_KEYS: "a1a5d8",
CONF_TIME: 5,
CONF_TIME_UNIT: "s",
@ -361,7 +334,7 @@ async def test_service_send_keys_hit_deferred(
DOMAIN,
LcnService.SEND_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_KEYS: "a1a5d8",
CONF_STATE: "make",
CONF_TIME: 5,
@ -371,11 +344,9 @@ async def test_service_send_keys_hit_deferred(
)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_lock_keys(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test lock_keys service."""
await async_setup_component(hass, "persistent_notification", {})
@ -386,7 +357,7 @@ async def test_service_lock_keys(
DOMAIN,
LcnService.LOCK_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_TABLE: "a",
CONF_STATE: "0011TT--",
},
@ -399,11 +370,9 @@ async def test_service_lock_keys(
lock_keys.assert_awaited_with(0, lock_states)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_lock_keys_tab_a_temporary(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test lock_keys (tab_a_temporary) service."""
await async_setup_component(hass, "persistent_notification", {})
@ -417,7 +386,7 @@ async def test_service_lock_keys_tab_a_temporary(
DOMAIN,
LcnService.LOCK_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_STATE: "0011TT--",
CONF_TIME: 10,
CONF_TIME_UNIT: "s",
@ -443,7 +412,7 @@ async def test_service_lock_keys_tab_a_temporary(
DOMAIN,
LcnService.LOCK_KEYS,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_TABLE: "b",
CONF_STATE: "0011TT--",
CONF_TIME: 10,
@ -453,11 +422,9 @@ async def test_service_lock_keys_tab_a_temporary(
)
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_dyn_text(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test dyn_text service."""
await async_setup_component(hass, "persistent_notification", {})
@ -468,7 +435,7 @@ async def test_service_dyn_text(
DOMAIN,
LcnService.DYN_TEXT,
{
**device_config(hass, entry, config_type),
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_ROW: 1,
CONF_TEXT: "text in row 1",
},
@ -478,11 +445,9 @@ async def test_service_dyn_text(
dyn_text.assert_awaited_with(0, "text in row 1")
@pytest.mark.parametrize("config_type", [CONF_ADDRESS, CONF_DEVICE_ID])
async def test_service_pck(
hass: HomeAssistant,
entry: MockConfigEntry,
config_type: str,
) -> None:
"""Test pck service."""
await async_setup_component(hass, "persistent_notification", {})
@ -492,43 +457,11 @@ async def test_service_pck(
await hass.services.async_call(
DOMAIN,
LcnService.PCK,
{**device_config(hass, entry, config_type), CONF_PCK: "PIN4"},
{
CONF_DEVICE_ID: get_device(hass, entry, (0, 7, False)).id,
CONF_PCK: "PIN4",
},
blocking=True,
)
pck.assert_awaited_with("PIN4")
async def test_service_called_with_invalid_host_id(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test service was called with non existing host id."""
await async_setup_component(hass, "persistent_notification", {})
await init_integration(hass, entry)
with patch.object(MockModuleConnection, "pck") as pck, pytest.raises(ValueError):
await hass.services.async_call(
DOMAIN,
LcnService.PCK,
{CONF_ADDRESS: "foobar.s0.m7", CONF_PCK: "PIN4"},
blocking=True,
)
pck.assert_not_awaited()
async def test_service_with_deprecated_address_parameter(
hass: HomeAssistant, entry: MockConfigEntry, issue_registry: ir.IssueRegistry
) -> None:
"""Test service puts issue in registry if called with address parameter."""
await async_setup_component(hass, "persistent_notification", {})
await init_integration(hass, entry)
await hass.services.async_call(
DOMAIN,
LcnService.PCK,
{CONF_ADDRESS: "pchk.s0.m7", CONF_PCK: "PIN4"},
blocking=True,
)
assert issue_registry.async_get_issue(DOMAIN, "deprecated_address_parameter")