diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index 256e132b30d..b3d2c14794c 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -24,12 +24,17 @@ from homeassistant.const import ( CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, + CONF_RESOURCE, CONF_USERNAME, Platform, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.typing import ConfigType from .const import ( @@ -38,6 +43,7 @@ from .const import ( CONF_DIM_MODE, CONF_DOMAIN_DATA, CONF_SK_NUM_TRIES, + CONF_TARGET_VALUE_LOCKED, CONF_TRANSITION, CONNECTION, DEVICE_CONNECTIONS, @@ -155,6 +161,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> if config_entry.minor_version < 2: new_data[CONF_ACKNOWLEDGE] = False + if config_entry.version < 2: # update to 2.1 (fix transitions for lights and switches) new_entities_data = [*new_data[CONF_ENTITIES]] for entity in new_entities_data: @@ -164,8 +171,19 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> entity[CONF_DOMAIN_DATA][CONF_TRANSITION] /= 1000.0 new_data[CONF_ENTITIES] = new_entities_data + if config_entry.version < 3: + # update to 3.1 (remove resource parameter, add climate target lock value parameter) + for entity in new_data[CONF_ENTITIES]: + entity.pop(CONF_RESOURCE, None) + + if entity[CONF_DOMAIN] == Platform.CLIMATE: + entity[CONF_DOMAIN_DATA].setdefault(CONF_TARGET_VALUE_LOCKED, -1) + + # migrate climate and scene unique ids + await async_migrate_entities(hass, config_entry) + hass.config_entries.async_update_entry( - config_entry, data=new_data, minor_version=1, version=2 + config_entry, data=new_data, minor_version=1, version=3 ) _LOGGER.debug( @@ -176,6 +194,29 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True +async def async_migrate_entities( + hass: HomeAssistant, config_entry: ConfigEntry +) -> None: + """Migrate entity registry.""" + + @callback + def update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None: + """Update unique ID of entity entry.""" + # fix unique entity ids for climate and scene + if "." in entity_entry.unique_id: + if entity_entry.domain == Platform.CLIMATE: + setpoint = entity_entry.unique_id.split(".")[-1] + return { + "new_unique_id": entity_entry.unique_id.rsplit("-", 1)[0] + + f"-{setpoint}" + } + if entity_entry.domain == Platform.SCENE: + return {"new_unique_id": entity_entry.unique_id.replace(".", "")} + return None + + await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + + async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Close connection to PCHK host represented by config_entry.""" # forward unloading to platforms diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py index 63e0d8c8b26..946c7ac3724 100644 --- a/homeassistant/components/lcn/config_flow.py +++ b/homeassistant/components/lcn/config_flow.py @@ -110,7 +110,7 @@ async def validate_connection(data: ConfigType) -> str | None: class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a LCN config flow.""" - VERSION = 2 + VERSION = 3 MINOR_VERSION = 1 async def async_step_user( diff --git a/homeassistant/components/lcn/entity.py b/homeassistant/components/lcn/entity.py index ffb680c4237..24897287449 100644 --- a/homeassistant/components/lcn/entity.py +++ b/homeassistant/components/lcn/entity.py @@ -3,18 +3,19 @@ from collections.abc import Callable from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_RESOURCE +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_NAME from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType -from .const import DOMAIN +from .const import CONF_DOMAIN_DATA, DOMAIN from .helpers import ( AddressType, DeviceConnectionType, InputType, generate_unique_id, get_device_connection, + get_resource, ) @@ -48,7 +49,11 @@ class LcnEntity(Entity): def unique_id(self) -> str: """Return a unique ID.""" return generate_unique_id( - self.config_entry.entry_id, self.address, self.config[CONF_RESOURCE] + self.config_entry.entry_id, + self.address, + get_resource( + self.config[CONF_DOMAIN], self.config[CONF_DOMAIN_DATA] + ).lower(), ) async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 2176c669251..a2796f88368 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -19,7 +19,6 @@ from homeassistant.const import ( CONF_ENTITIES, CONF_LIGHTS, CONF_NAME, - CONF_RESOURCE, CONF_SENSORS, CONF_SWITCHES, ) @@ -29,6 +28,7 @@ from homeassistant.helpers.typing import ConfigType from .const import ( CONF_CLIMATES, + CONF_DOMAIN_DATA, CONF_HARDWARE_SERIAL, CONF_HARDWARE_TYPE, CONF_SCENES, @@ -79,9 +79,9 @@ def get_resource(domain_name: str, domain_data: ConfigType) -> str: if domain_name == "cover": return cast(str, domain_data["motor"]) if domain_name == "climate": - return f"{domain_data['source']}.{domain_data['setpoint']}" + return cast(str, domain_data["setpoint"]) if domain_name == "scene": - return f"{domain_data['register']}.{domain_data['scene']}" + return f"{domain_data['register']}{domain_data['scene']}" raise ValueError("Unknown domain") @@ -115,7 +115,9 @@ def purge_entity_registry( references_entry_data = set() for entity_data in imported_entry_data[CONF_ENTITIES]: entity_unique_id = generate_unique_id( - entry_id, entity_data[CONF_ADDRESS], entity_data[CONF_RESOURCE] + entry_id, + entity_data[CONF_ADDRESS], + get_resource(entity_data[CONF_DOMAIN], entity_data[CONF_DOMAIN_DATA]), ) entity_id = entity_registry.async_get_entity_id( entity_data[CONF_DOMAIN], DOMAIN, entity_unique_id diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index c1dd7751940..e5313eee4f3 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -8,5 +8,5 @@ "documentation": "https://www.home-assistant.io/integrations/lcn", "iot_class": "local_push", "loggers": ["pypck"], - "requirements": ["pypck==0.8.5", "lcn-frontend==0.2.3"] + "requirements": ["pypck==0.8.5", "lcn-frontend==0.2.4"] } diff --git a/homeassistant/components/lcn/websocket.py b/homeassistant/components/lcn/websocket.py index 9084ec838d9..545ee1e0043 100644 --- a/homeassistant/components/lcn/websocket.py +++ b/homeassistant/components/lcn/websocket.py @@ -19,7 +19,6 @@ from homeassistant.const import ( CONF_DOMAIN, CONF_ENTITIES, CONF_NAME, - CONF_RESOURCE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import ( @@ -343,7 +342,6 @@ async def websocket_add_entity( entity_config = { CONF_ADDRESS: msg[CONF_ADDRESS], CONF_NAME: msg[CONF_NAME], - CONF_RESOURCE: resource, CONF_DOMAIN: domain_name, CONF_DOMAIN_DATA: domain_data, } @@ -371,7 +369,15 @@ async def websocket_add_entity( vol.Required("entry_id"): cv.string, vol.Required(CONF_ADDRESS): ADDRESS_SCHEMA, vol.Required(CONF_DOMAIN): cv.string, - vol.Required(CONF_RESOURCE): cv.string, + vol.Required(CONF_DOMAIN_DATA): vol.Any( + DOMAIN_DATA_BINARY_SENSOR, + DOMAIN_DATA_SENSOR, + DOMAIN_DATA_SWITCH, + DOMAIN_DATA_LIGHT, + DOMAIN_DATA_CLIMATE, + DOMAIN_DATA_COVER, + DOMAIN_DATA_SCENE, + ), } ) @websocket_api.async_response @@ -390,7 +396,10 @@ async def websocket_delete_entity( if ( tuple(entity_config[CONF_ADDRESS]) == msg[CONF_ADDRESS] and entity_config[CONF_DOMAIN] == msg[CONF_DOMAIN] - and entity_config[CONF_RESOURCE] == msg[CONF_RESOURCE] + and get_resource( + entity_config[CONF_DOMAIN], entity_config[CONF_DOMAIN_DATA] + ) + == get_resource(msg[CONF_DOMAIN], msg[CONF_DOMAIN_DATA]) ) ), None, diff --git a/requirements_all.txt b/requirements_all.txt index 491630bba56..73e0ca1cca9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1308,7 +1308,7 @@ lakeside==0.13 laundrify-aio==1.2.2 # homeassistant.components.lcn -lcn-frontend==0.2.3 +lcn-frontend==0.2.4 # homeassistant.components.ld2410_ble ld2410-ble==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd6766fae56..6e5575099b7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1108,7 +1108,7 @@ lacrosse-view==1.1.1 laundrify-aio==1.2.2 # homeassistant.components.lcn -lcn-frontend==0.2.3 +lcn-frontend==0.2.4 # homeassistant.components.ld2410_ble ld2410-ble==0.1.1 diff --git a/tests/components/lcn/conftest.py b/tests/components/lcn/conftest.py index d8dee472946..e588cc7b952 100644 --- a/tests/components/lcn/conftest.py +++ b/tests/components/lcn/conftest.py @@ -88,7 +88,7 @@ def create_config_entry( title = entry_data[CONF_HOST] return MockConfigEntry( - entry_id=fixture_filename, + entry_id=fixture_filename.replace(".", "_"), domain=DOMAIN, title=title, data=entry_data, diff --git a/tests/components/lcn/fixtures/config_entry_pchk.json b/tests/components/lcn/fixtures/config_entry_pchk.json index 068b8757707..f319e37b265 100644 --- a/tests/components/lcn/fixtures/config_entry_pchk.json +++ b/tests/components/lcn/fixtures/config_entry_pchk.json @@ -27,7 +27,6 @@ { "address": [0, 7, false], "name": "Light_Output1", - "resource": "output1", "domain": "light", "domain_data": { "output": "OUTPUT1", @@ -38,7 +37,6 @@ { "address": [0, 7, false], "name": "Light_Output2", - "resource": "output2", "domain": "light", "domain_data": { "output": "OUTPUT2", @@ -49,7 +47,6 @@ { "address": [0, 7, false], "name": "Light_Relay1", - "resource": "relay1", "domain": "light", "domain_data": { "output": "RELAY1", @@ -60,7 +57,6 @@ { "address": [0, 7, false], "name": "Switch_Output1", - "resource": "output1", "domain": "switch", "domain_data": { "output": "OUTPUT1" @@ -69,7 +65,6 @@ { "address": [0, 7, false], "name": "Switch_Output2", - "resource": "output2", "domain": "switch", "domain_data": { "output": "OUTPUT2" @@ -78,7 +73,6 @@ { "address": [0, 7, false], "name": "Switch_Relay1", - "resource": "relay1", "domain": "switch", "domain_data": { "output": "RELAY1" @@ -87,7 +81,6 @@ { "address": [0, 7, false], "name": "Switch_Relay2", - "resource": "relay2", "domain": "switch", "domain_data": { "output": "RELAY2" @@ -96,7 +89,6 @@ { "address": [0, 7, false], "name": "Switch_Regulator1", - "resource": "r1varsetpoint", "domain": "switch", "domain_data": { "output": "R1VARSETPOINT" @@ -105,7 +97,6 @@ { "address": [0, 7, false], "name": "Switch_KeyLock1", - "resource": "a1", "domain": "switch", "domain_data": { "output": "A1" @@ -114,7 +105,6 @@ { "address": [0, 5, true], "name": "Switch_Group5", - "resource": "relay1", "domain": "switch", "domain_data": { "output": "RELAY1" @@ -123,7 +113,6 @@ { "address": [0, 7, false], "name": "Cover_Outputs", - "resource": "outputs", "domain": "cover", "domain_data": { "motor": "OUTPUTS", @@ -133,7 +122,6 @@ { "address": [0, 7, false], "name": "Cover_Relays", - "resource": "motor1", "domain": "cover", "domain_data": { "motor": "MOTOR1", @@ -143,12 +131,12 @@ { "address": [0, 7, false], "name": "Climate1", - "resource": "var1.r1varsetpoint", "domain": "climate", "domain_data": { "source": "VAR1", "setpoint": "R1VARSETPOINT", "lockable": true, + "target_value_locked": -1, "min_temp": 0.0, "max_temp": 40.0, "unit_of_measurement": "°C" @@ -157,7 +145,6 @@ { "address": [0, 7, false], "name": "Romantic", - "resource": "0.0", "domain": "scene", "domain_data": { "register": 0, @@ -169,7 +156,6 @@ { "address": [0, 7, false], "name": "Romantic Transition", - "resource": "0.1", "domain": "scene", "domain_data": { "register": 0, @@ -181,7 +167,6 @@ { "address": [0, 7, false], "name": "Sensor_LockRegulator1", - "resource": "r1varsetpoint", "domain": "binary_sensor", "domain_data": { "source": "R1VARSETPOINT" @@ -190,7 +175,6 @@ { "address": [0, 7, false], "name": "Binary_Sensor1", - "resource": "binsensor1", "domain": "binary_sensor", "domain_data": { "source": "BINSENSOR1" @@ -199,7 +183,6 @@ { "address": [0, 7, false], "name": "Sensor_KeyLock", - "resource": "a5", "domain": "binary_sensor", "domain_data": { "source": "A5" @@ -208,7 +191,6 @@ { "address": [0, 7, false], "name": "Sensor_Var1", - "resource": "var1", "domain": "sensor", "domain_data": { "source": "VAR1", @@ -218,7 +200,6 @@ { "address": [0, 7, false], "name": "Sensor_Setpoint1", - "resource": "r1varsetpoint", "domain": "sensor", "domain_data": { "source": "R1VARSETPOINT", @@ -228,7 +209,6 @@ { "address": [0, 7, false], "name": "Sensor_Led6", - "resource": "led6", "domain": "sensor", "domain_data": { "source": "LED6", @@ -238,7 +218,6 @@ { "address": [0, 7, false], "name": "Sensor_LogicOp1", - "resource": "logicop1", "domain": "sensor", "domain_data": { "source": "LOGICOP1", diff --git a/tests/components/lcn/fixtures/config_entry_pchk_v2_1.json b/tests/components/lcn/fixtures/config_entry_pchk_v2_1.json new file mode 100644 index 00000000000..3b4938b8600 --- /dev/null +++ b/tests/components/lcn/fixtures/config_entry_pchk_v2_1.json @@ -0,0 +1,96 @@ +{ + "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 + } + ], + "entities": [ + { + "address": [0, 7, false], + "name": "Light_Output1", + "resource": "output1", + "domain": "light", + "domain_data": { + "output": "OUTPUT1", + "dimmable": true, + "transition": 5.0 + } + }, + { + "address": [0, 7, false], + "name": "Switch_Relay1", + "resource": "relay1", + "domain": "switch", + "domain_data": { + "output": "RELAY1" + } + }, + { + "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": 0.0 + } + }, + { + "address": [0, 7, false], + "name": "Binary_Sensor1", + "resource": "binsensor1", + "domain": "binary_sensor", + "domain_data": { + "source": "BINSENSOR1" + } + }, + { + "address": [0, 7, false], + "name": "Sensor_Var1", + "resource": "var1", + "domain": "sensor", + "domain_data": { + "source": "VAR1", + "unit_of_measurement": "°C" + } + } + ] +} diff --git a/tests/components/lcn/snapshots/test_binary_sensor.ambr b/tests/components/lcn/snapshots/test_binary_sensor.ambr index d2d697569d1..383c9038d78 100644 --- a/tests/components/lcn/snapshots/test_binary_sensor.ambr +++ b/tests/components/lcn/snapshots/test_binary_sensor.ambr @@ -29,7 +29,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-binsensor1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-binsensor1', 'unit_of_measurement': None, }) # --- @@ -76,7 +76,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-a5', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-a5', 'unit_of_measurement': None, }) # --- @@ -123,7 +123,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-r1varsetpoint', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-r1varsetpoint', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/snapshots/test_climate.ambr b/tests/components/lcn/snapshots/test_climate.ambr index 81745ca8515..bd371c02492 100644 --- a/tests/components/lcn/snapshots/test_climate.ambr +++ b/tests/components/lcn/snapshots/test_climate.ambr @@ -36,7 +36,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-var1.r1varsetpoint', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-r1varsetpoint', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/snapshots/test_cover.ambr b/tests/components/lcn/snapshots/test_cover.ambr index d399626537d..3e9c4ee72eb 100644 --- a/tests/components/lcn/snapshots/test_cover.ambr +++ b/tests/components/lcn/snapshots/test_cover.ambr @@ -29,7 +29,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-outputs', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-outputs', 'unit_of_measurement': None, }) # --- @@ -78,7 +78,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-motor1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-motor1', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/snapshots/test_init.ambr b/tests/components/lcn/snapshots/test_init.ambr index ea6267aaa0b..8d7a858cf16 100644 --- a/tests/components/lcn/snapshots/test_init.ambr +++ b/tests/components/lcn/snapshots/test_init.ambr @@ -30,7 +30,6 @@ 'transition': 5.0, }), 'name': 'Light_Output1', - 'resource': 'output1', }), ]), 'host': 'pchk', @@ -72,7 +71,6 @@ 'transition': 5.0, }), 'name': 'Light_Output1', - 'resource': 'output1', }), dict({ 'address': tuple( @@ -87,7 +85,6 @@ 'transition': 0.0, }), 'name': 'Light_Output2', - 'resource': 'output2', }), dict({ 'address': tuple( @@ -107,7 +104,6 @@ 'transition': 0.0, }), 'name': 'Romantic', - 'resource': '0.0', }), dict({ 'address': tuple( @@ -127,7 +123,134 @@ 'transition': 10.0, }), 'name': 'Romantic Transition', - 'resource': '0.1', + }), + ]), + 'host': 'pchk', + 'ip_address': '192.168.2.41', + 'password': 'lcn', + 'port': 4114, + 'sk_num_tries': 0, + 'username': 'lcn', + }) +# --- +# name: test_migrate_2_1 + dict({ + 'acknowledge': False, + 'devices': list([ + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'hardware_serial': -1, + 'hardware_type': -1, + 'name': 'TestModule', + 'software_serial': -1, + }), + ]), + 'dim_mode': 'STEPS200', + 'entities': list([ + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'light', + 'domain_data': dict({ + 'dimmable': True, + 'output': 'OUTPUT1', + 'transition': 5.0, + }), + 'name': 'Light_Output1', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'switch', + 'domain_data': dict({ + 'output': 'RELAY1', + }), + 'name': 'Switch_Relay1', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'cover', + 'domain_data': dict({ + 'motor': 'MOTOR1', + 'reverse_time': 'RT1200', + }), + 'name': 'Cover_Relays', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'climate', + 'domain_data': dict({ + 'lockable': True, + 'max_temp': 40.0, + 'min_temp': 0.0, + 'setpoint': 'R1VARSETPOINT', + 'source': 'VAR1', + 'target_value_locked': -1, + 'unit_of_measurement': '°C', + }), + 'name': 'Climate1', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'scene', + 'domain_data': dict({ + 'outputs': list([ + 'OUTPUT1', + 'OUTPUT2', + 'RELAY1', + ]), + 'register': 0, + 'scene': 0, + 'transition': 0.0, + }), + 'name': 'Romantic', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'binary_sensor', + 'domain_data': dict({ + 'source': 'BINSENSOR1', + }), + 'name': 'Binary_Sensor1', + }), + dict({ + 'address': tuple( + 0, + 7, + False, + ), + 'domain': 'sensor', + 'domain_data': dict({ + 'source': 'VAR1', + 'unit_of_measurement': '°C', + }), + 'name': 'Sensor_Var1', }), ]), 'host': 'pchk', diff --git a/tests/components/lcn/snapshots/test_light.ambr b/tests/components/lcn/snapshots/test_light.ambr index 638cddc15cd..5bfd00fb0d7 100644 --- a/tests/components/lcn/snapshots/test_light.ambr +++ b/tests/components/lcn/snapshots/test_light.ambr @@ -33,7 +33,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-output1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-output1', 'unit_of_measurement': None, }) # --- @@ -90,7 +90,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-output2', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-output2', 'unit_of_measurement': None, }) # --- @@ -146,7 +146,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-relay1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-relay1', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/snapshots/test_scene.ambr b/tests/components/lcn/snapshots/test_scene.ambr index a5576158621..6dac4868437 100644 --- a/tests/components/lcn/snapshots/test_scene.ambr +++ b/tests/components/lcn/snapshots/test_scene.ambr @@ -29,7 +29,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-0.0', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-00', 'unit_of_measurement': None, }) # --- @@ -76,7 +76,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-0.1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-01', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/snapshots/test_sensor.ambr b/tests/components/lcn/snapshots/test_sensor.ambr index f8d57ed8904..1e172dda7e9 100644 --- a/tests/components/lcn/snapshots/test_sensor.ambr +++ b/tests/components/lcn/snapshots/test_sensor.ambr @@ -29,7 +29,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-led6', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-led6', 'unit_of_measurement': None, }) # --- @@ -76,7 +76,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-logicop1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-logicop1', 'unit_of_measurement': None, }) # --- @@ -123,7 +123,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-r1varsetpoint', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-r1varsetpoint', 'unit_of_measurement': , }) # --- @@ -172,7 +172,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-var1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-var1', 'unit_of_measurement': , }) # --- diff --git a/tests/components/lcn/snapshots/test_switch.ambr b/tests/components/lcn/snapshots/test_switch.ambr index bc69b0ed483..7ba943a671f 100644 --- a/tests/components/lcn/snapshots/test_switch.ambr +++ b/tests/components/lcn/snapshots/test_switch.ambr @@ -29,7 +29,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-g000005-relay1', + 'unique_id': 'lcn/config_entry_pchk_json-g000005-relay1', 'unit_of_measurement': None, }) # --- @@ -76,7 +76,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-a1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-a1', 'unit_of_measurement': None, }) # --- @@ -123,7 +123,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-output1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-output1', 'unit_of_measurement': None, }) # --- @@ -170,7 +170,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-output2', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-output2', 'unit_of_measurement': None, }) # --- @@ -217,7 +217,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-r1varsetpoint', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-r1varsetpoint', 'unit_of_measurement': None, }) # --- @@ -264,7 +264,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-relay1', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-relay1', 'unit_of_measurement': None, }) # --- @@ -311,7 +311,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'lcn/config_entry_pchk.json-m000007-relay2', + 'unique_id': 'lcn/config_entry_pchk_json-m000007-relay2', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/lcn/test_init.py b/tests/components/lcn/test_init.py index ef3c2d3cb66..da967782539 100644 --- a/tests/components/lcn/test_init.py +++ b/tests/components/lcn/test_init.py @@ -138,15 +138,12 @@ async def test_async_entry_reload_on_host_event_received( async def test_migrate_1_1(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: """Test migration config entry.""" entry_v1_1 = create_config_entry("pchk_v1_1", version=(1, 1)) - entry_v1_1.add_to_hass(hass) - - await hass.config_entries.async_setup(entry_v1_1.entry_id) - await hass.async_block_till_done() + await init_integration(hass, entry_v1_1) entry_migrated = hass.config_entries.async_get_entry(entry_v1_1.entry_id) assert entry_migrated.state is ConfigEntryState.LOADED - assert entry_migrated.version == 2 + assert entry_migrated.version == 3 assert entry_migrated.minor_version == 1 assert entry_migrated.data == snapshot @@ -155,14 +152,51 @@ async def test_migrate_1_1(hass: HomeAssistant, snapshot: SnapshotAssertion) -> async def test_migrate_1_2(hass: HomeAssistant, snapshot: SnapshotAssertion) -> 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() + await init_integration(hass, entry_v1_2) 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.version == 3 assert entry_migrated.minor_version == 1 assert entry_migrated.data == snapshot + + +@patch("homeassistant.components.lcn.PchkConnectionManager", MockPchkConnectionManager) +async def test_migrate_2_1(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Test migration config entry.""" + entry_v2_1 = create_config_entry("pchk_v2_1", version=(2, 1)) + await init_integration(hass, entry_v2_1) + + entry_migrated = hass.config_entries.async_get_entry(entry_v2_1.entry_id) + assert entry_migrated.state is ConfigEntryState.LOADED + assert entry_migrated.version == 3 + assert entry_migrated.minor_version == 1 + assert entry_migrated.data == snapshot + + +@pytest.mark.parametrize( + ("entity_id", "replace"), + [ + ("climate.climate1", ("-r1varsetpoint", "-var1.r1varsetpoint")), + ("scene.romantic", ("-00", "-0.0")), + ], +) +@patch("homeassistant.components.lcn.PchkConnectionManager", MockPchkConnectionManager) +async def test_entity_migration_on_2_1( + hass: HomeAssistant, entity_registry: er.EntityRegistry, entity_id, replace +) -> None: + """Test entity.unique_id migration on config_entry migration from 2.1.""" + entry_v2_1 = create_config_entry("pchk_v2_1", version=(2, 1)) + await init_integration(hass, entry_v2_1) + + migrated_unique_id = entity_registry.async_get(entity_id).unique_id + old_unique_id = migrated_unique_id.replace(*replace) + entity_registry.async_update_entity(entity_id, new_unique_id=old_unique_id) + assert entity_registry.async_get(entity_id).unique_id == old_unique_id + + await hass.config_entries.async_unload(entry_v2_1.entry_id) + + entry_v2_1 = create_config_entry("pchk_v2_1", version=(2, 1)) + await init_integration(hass, entry_v2_1) + assert entity_registry.async_get(entity_id).unique_id == migrated_unique_id diff --git a/tests/components/lcn/test_websocket.py b/tests/components/lcn/test_websocket.py index 2c5fff89e19..02bf6b4c546 100644 --- a/tests/components/lcn/test_websocket.py +++ b/tests/components/lcn/test_websocket.py @@ -7,14 +7,13 @@ import pytest from homeassistant.components.lcn import AddressType from homeassistant.components.lcn.const import CONF_DOMAIN_DATA -from homeassistant.components.lcn.helpers import get_device_config, get_resource +from homeassistant.components.lcn.helpers import get_device_config from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICES, CONF_DOMAIN, CONF_ENTITIES, CONF_NAME, - CONF_RESOURCE, CONF_TYPE, ) from homeassistant.core import HomeAssistant @@ -52,7 +51,7 @@ ENTITIES_DELETE_PAYLOAD = { "entry_id": "", CONF_ADDRESS: (0, 7, False), CONF_DOMAIN: "switch", - CONF_RESOURCE: "relay1", + CONF_DOMAIN_DATA: {"output": "RELAY1"}, } @@ -184,18 +183,14 @@ async def test_lcn_entities_add_command( for key in (CONF_ADDRESS, CONF_NAME, CONF_DOMAIN, CONF_DOMAIN_DATA) } - resource = get_resource( - ENTITIES_ADD_PAYLOAD[CONF_DOMAIN], ENTITIES_ADD_PAYLOAD[CONF_DOMAIN_DATA] - ).lower() - - assert {**entity_config, CONF_RESOURCE: resource} not in entry.data[CONF_ENTITIES] + assert entity_config not in entry.data[CONF_ENTITIES] await client.send_json_auto_id({**ENTITIES_ADD_PAYLOAD, "entry_id": entry.entry_id}) res = await client.receive_json() assert res["success"], res - assert {**entity_config, CONF_RESOURCE: resource} in entry.data[CONF_ENTITIES] + assert entity_config in entry.data[CONF_ENTITIES] async def test_lcn_entities_delete_command( @@ -213,7 +208,8 @@ async def test_lcn_entities_delete_command( for entity in entry.data[CONF_ENTITIES] if entity[CONF_ADDRESS] == ENTITIES_DELETE_PAYLOAD[CONF_ADDRESS] and entity[CONF_DOMAIN] == ENTITIES_DELETE_PAYLOAD[CONF_DOMAIN] - and entity[CONF_RESOURCE] == ENTITIES_DELETE_PAYLOAD[CONF_RESOURCE] + and entity[CONF_DOMAIN_DATA] + == ENTITIES_DELETE_PAYLOAD[CONF_DOMAIN_DATA] ] ) == 1 @@ -233,7 +229,8 @@ async def test_lcn_entities_delete_command( for entity in entry.data[CONF_ENTITIES] if entity[CONF_ADDRESS] == ENTITIES_DELETE_PAYLOAD[CONF_ADDRESS] and entity[CONF_DOMAIN] == ENTITIES_DELETE_PAYLOAD[CONF_DOMAIN] - and entity[CONF_RESOURCE] == ENTITIES_DELETE_PAYLOAD[CONF_RESOURCE] + and entity[CONF_DOMAIN_DATA] + == ENTITIES_DELETE_PAYLOAD[CONF_DOMAIN_DATA] ] ) == 0