Fix transition config storage in LCN light and scene platform (#127847)

This commit is contained in:
Andre Lengwenus 2024-10-25 16:25:41 +02:00 committed by GitHub
parent 519a888e82
commit 759fe54132
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 282 additions and 25 deletions

View File

@ -11,10 +11,13 @@ from pypck.connection import PchkConnectionManager
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITIES,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
@ -24,7 +27,9 @@ from .const import (
ADD_ENTITIES_CALLBACKS,
CONF_ACKNOWLEDGE,
CONF_DIM_MODE,
CONF_DOMAIN_DATA,
CONF_SK_NUM_TRIES,
CONF_TRANSITION,
CONNECTION,
DOMAIN,
PLATFORMS,
@ -147,15 +152,25 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
config_entry.minor_version,
)
if config_entry.version == 1:
new_data = {**config_entry.data}
new_data = {**config_entry.data}
if config_entry.version == 1:
# update to 1.2 (add acknowledge flag)
if config_entry.minor_version < 2:
new_data[CONF_ACKNOWLEDGE] = False
hass.config_entries.async_update_entry(
config_entry, data=new_data, minor_version=2, version=1
)
# update to 2.1 (fix transitions for lights and switches)
new_entities_data = [*new_data[CONF_ENTITIES]]
for entity in new_entities_data:
if entity[CONF_DOMAIN] in [Platform.LIGHT, Platform.SCENE]:
if entity[CONF_DOMAIN_DATA][CONF_TRANSITION] is None:
entity[CONF_DOMAIN_DATA][CONF_TRANSITION] = 0
entity[CONF_DOMAIN_DATA][CONF_TRANSITION] /= 1000.0
new_data[CONF_ENTITIES] = new_entities_data
hass.config_entries.async_update_entry(
config_entry, data=new_data, minor_version=1, version=2
)
_LOGGER.debug(
"Migration to configuration version %s.%s successful",

View File

@ -110,8 +110,8 @@ async def validate_connection(data: ConfigType) -> str | None:
class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a LCN config flow."""
VERSION = 1
MINOR_VERSION = 2
VERSION = 2
MINOR_VERSION = 1
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Import existing configuration from LCN."""

View File

@ -90,7 +90,7 @@ class LcnOutputLight(LcnEntity, LightEntity):
self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]]
self._transition = pypck.lcn_defs.time_to_ramp_value(
config[CONF_DOMAIN_DATA][CONF_TRANSITION]
config[CONF_DOMAIN_DATA][CONF_TRANSITION] * 1000.0
)
self.dimmable = config[CONF_DOMAIN_DATA][CONF_DIMMABLE]

View File

@ -87,7 +87,7 @@ class LcnScene(LcnEntity, Scene):
self.transition = None
else:
self.transition = pypck.lcn_defs.time_to_ramp_value(
config[CONF_DOMAIN_DATA][CONF_TRANSITION]
config[CONF_DOMAIN_DATA][CONF_TRANSITION] * 1000.0
)
async def async_activate(self, **kwargs: Any) -> None:

View File

@ -95,7 +95,7 @@ DOMAIN_DATA_LIGHT: VolDictType = {
vol.Required(CONF_OUTPUT): vol.All(vol.Upper, vol.In(OUTPUT_PORTS + RELAY_PORTS)),
vol.Optional(CONF_DIMMABLE, default=False): vol.Coerce(bool),
vol.Optional(CONF_TRANSITION, default=0): vol.All(
vol.Coerce(float), vol.Range(min=0.0, max=486.0), lambda value: value * 1000
vol.Coerce(float), vol.Range(min=0.0, max=486.0)
),
}
@ -106,13 +106,8 @@ DOMAIN_DATA_SCENE: VolDictType = {
vol.Optional(CONF_OUTPUTS, default=[]): vol.All(
cv.ensure_list, [vol.All(vol.Upper, vol.In(OUTPUT_PORTS + RELAY_PORTS))]
),
vol.Optional(CONF_TRANSITION, default=None): vol.Any(
vol.All(
vol.Coerce(int),
vol.Range(min=0.0, max=486.0),
lambda value: value * 1000,
),
None,
vol.Optional(CONF_TRANSITION, default=0): vol.Any(
vol.All(vol.Coerce(int), vol.Range(min=0.0, max=486.0))
),
}

View File

@ -32,7 +32,7 @@
"domain_data": {
"output": "OUTPUT1",
"dimmable": true,
"transition": 5000.0
"transition": 5.0
}
},
{
@ -43,7 +43,7 @@
"domain_data": {
"output": "OUTPUT2",
"dimmable": false,
"transition": 0
"transition": 0.0
}
},
{
@ -145,7 +145,7 @@
"register": 0,
"scene": 0,
"outputs": ["OUTPUT1", "OUTPUT2", "RELAY1"],
"transition": null
"transition": 0.0
}
},
{
@ -157,7 +157,7 @@
"register": 0,
"scene": 1,
"outputs": ["OUTPUT1", "OUTPUT2", "RELAY1"],
"transition": 10
"transition": 10.0
}
},
{

View File

@ -156,7 +156,7 @@
"register": 0,
"scene": 1,
"outputs": ["OUTPUT1", "OUTPUT2", "RELAY1"],
"transition": 10
"transition": 10000
}
},
{

View File

@ -0,0 +1,231 @@
{
"host": "pchk",
"ip_address": "192.168.2.41",
"port": 4114,
"username": "lcn",
"password": "lcn",
"sk_num_tries": 0,
"dim_mode": "STEPS200",
"acknowledge": false,
"devices": [
{
"address": [0, 7, false],
"name": "TestModule",
"hardware_serial": -1,
"software_serial": -1,
"hardware_type": -1
},
{
"address": [0, 5, true],
"name": "TestGroup",
"hardware_serial": -1,
"software_serial": -1,
"hardware_type": -1
}
],
"entities": [
{
"address": [0, 7, false],
"name": "Light_Output1",
"resource": "output1",
"domain": "light",
"domain_data": {
"output": "OUTPUT1",
"dimmable": true,
"transition": 5000.0
}
},
{
"address": [0, 7, false],
"name": "Light_Output2",
"resource": "output2",
"domain": "light",
"domain_data": {
"output": "OUTPUT2",
"dimmable": false,
"transition": 0
}
},
{
"address": [0, 7, false],
"name": "Light_Relay1",
"resource": "relay1",
"domain": "light",
"domain_data": {
"output": "RELAY1",
"dimmable": false,
"transition": 0.0
}
},
{
"address": [0, 7, false],
"name": "Switch_Output1",
"resource": "output1",
"domain": "switch",
"domain_data": {
"output": "OUTPUT1"
}
},
{
"address": [0, 7, false],
"name": "Switch_Output2",
"resource": "output2",
"domain": "switch",
"domain_data": {
"output": "OUTPUT2"
}
},
{
"address": [0, 7, false],
"name": "Switch_Relay1",
"resource": "relay1",
"domain": "switch",
"domain_data": {
"output": "RELAY1"
}
},
{
"address": [0, 7, false],
"name": "Switch_Relay2",
"resource": "relay2",
"domain": "switch",
"domain_data": {
"output": "RELAY2"
}
},
{
"address": [0, 5, true],
"name": "Switch_Group5",
"resource": "relay1",
"domain": "switch",
"domain_data": {
"output": "RELAY1"
}
},
{
"address": [0, 7, false],
"name": "Cover_Outputs",
"resource": "outputs",
"domain": "cover",
"domain_data": {
"motor": "OUTPUTS",
"reverse_time": "RT1200"
}
},
{
"address": [0, 7, false],
"name": "Cover_Relays",
"resource": "motor1",
"domain": "cover",
"domain_data": {
"motor": "MOTOR1",
"reverse_time": "RT1200"
}
},
{
"address": [0, 7, false],
"name": "Climate1",
"resource": "var1.r1varsetpoint",
"domain": "climate",
"domain_data": {
"source": "VAR1",
"setpoint": "R1VARSETPOINT",
"lockable": true,
"min_temp": 0.0,
"max_temp": 40.0,
"unit_of_measurement": "°C"
}
},
{
"address": [0, 7, false],
"name": "Romantic",
"resource": "0.0",
"domain": "scene",
"domain_data": {
"register": 0,
"scene": 0,
"outputs": ["OUTPUT1", "OUTPUT2", "RELAY1"],
"transition": null
}
},
{
"address": [0, 7, false],
"name": "Romantic Transition",
"resource": "0.1",
"domain": "scene",
"domain_data": {
"register": 0,
"scene": 1,
"outputs": ["OUTPUT1", "OUTPUT2", "RELAY1"],
"transition": 10000
}
},
{
"address": [0, 7, false],
"name": "Sensor_LockRegulator1",
"resource": "r1varsetpoint",
"domain": "binary_sensor",
"domain_data": {
"source": "R1VARSETPOINT"
}
},
{
"address": [0, 7, false],
"name": "Binary_Sensor1",
"resource": "binsensor1",
"domain": "binary_sensor",
"domain_data": {
"source": "BINSENSOR1"
}
},
{
"address": [0, 7, false],
"name": "Sensor_KeyLock",
"resource": "a5",
"domain": "binary_sensor",
"domain_data": {
"source": "A5"
}
},
{
"address": [0, 7, false],
"name": "Sensor_Var1",
"resource": "var1",
"domain": "sensor",
"domain_data": {
"source": "VAR1",
"unit_of_measurement": "°C"
}
},
{
"address": [0, 7, false],
"name": "Sensor_Setpoint1",
"resource": "r1varsetpoint",
"domain": "sensor",
"domain_data": {
"source": "R1VARSETPOINT",
"unit_of_measurement": "°C"
}
},
{
"address": [0, 7, false],
"name": "Sensor_Led6",
"resource": "led6",
"domain": "sensor",
"domain_data": {
"source": "LED6",
"unit_of_measurement": "NATIVE"
}
},
{
"address": [0, 7, false],
"name": "Sensor_LogicOp1",
"resource": "logicop1",
"domain": "sensor",
"domain_data": {
"source": "LOGICOP1",
"unit_of_measurement": "NATIVE"
}
}
]
}

View File

@ -139,6 +139,22 @@ async def test_migrate_1_1(hass: HomeAssistant, entry) -> None:
entry_migrated = hass.config_entries.async_get_entry(entry_v1_1.entry_id)
assert entry_migrated.state is ConfigEntryState.LOADED
assert entry_migrated.version == 1
assert entry_migrated.minor_version == 2
assert entry_migrated.version == 2
assert entry_migrated.minor_version == 1
assert entry_migrated.data == entry.data
@patch("homeassistant.components.lcn.PchkConnectionManager", MockPchkConnectionManager)
async def test_migrate_1_2(hass: HomeAssistant, entry) -> None:
"""Test migration config entry."""
entry_v1_2 = create_config_entry("pchk_v1_2", version=(1, 2))
entry_v1_2.add_to_hass(hass)
await hass.config_entries.async_setup(entry_v1_2.entry_id)
await hass.async_block_till_done()
entry_migrated = hass.config_entries.async_get_entry(entry_v1_2.entry_id)
assert entry_migrated.state is ConfigEntryState.LOADED
assert entry_migrated.version == 2
assert entry_migrated.minor_version == 1
assert entry_migrated.data == entry.data

View File

@ -51,7 +51,7 @@ async def test_scene_activate(
assert state is not None
activate_scene.assert_awaited_with(
0, 0, [OutputPort.OUTPUT1, OutputPort.OUTPUT2], [RelayPort.RELAY1], None
0, 0, [OutputPort.OUTPUT1, OutputPort.OUTPUT2], [RelayPort.RELAY1], 0.0
)