Przeglądaj źródła

Migrate to config_id based on type and name as index in config.

Standardize names to limit the translations required for config flow.
Jason Rumney 4 lat temu
rodzic
commit
b7e34c729b

+ 26 - 0
custom_components/tuya_local/__init__.py

@@ -101,6 +101,32 @@ async def async_migrate_entry(hass, entry: ConfigEntry):
         }
         }
         entry.version = 4
         entry.version = 4
 
 
+    if entry.version == 4:
+        # Migrate indexes to entity id rather than type, to allow for multiple
+        # entities of the same type for a device.
+        config = {**entry.data, **entry.options, "name": entry.title}
+        devcfg = get_config(config[CONF_TYPE])
+        opts = {**entry.options}
+        newopts = {**opts}
+        entry.data = {
+            CONF_DEVICE_ID: config[CONF_DEVICE_ID],
+            CONF_LOCAL_KEY: config[CONF_LOCAL_KEY],
+            CONF_HOST: config[CONF_HOST],
+            CONF_TYPE: config[CONF_TYPE],
+        }
+        e = devcfg.primary_entity
+        if e.config_id != e.entity:
+            newopts.pop(e.entity)
+            newopts[e.config_id] = opts[e.entity]
+
+        for e in devcfg.secondary_entities():
+            if e.config_id != e.entity:
+                newopts.pop(e.entity)
+                newopts[e.config_id] = opts[e.entity]
+
+        entry.options = {**newopts}
+        entry.version = 5
+
     return True
     return True
 
 
 
 

+ 1 - 1
custom_components/tuya_local/devices/electriq_cd12pw_dehumidifier.yaml

@@ -41,7 +41,7 @@ primary_entity:
       readonly: true
       readonly: true
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
-    name: "Humidity Indicator"
+    name: Display
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean

+ 2 - 2
custom_components/tuya_local/devices/electriq_cd20pro_dehumidifier.yaml

@@ -66,14 +66,14 @@ secondary_entities:
           - dps_val: "0_90"
           - dps_val: "0_90"
             value: "Oscillate"
             value: "Oscillate"
   - entity: switch
   - entity: switch
-    name: "UV sanitizer"
+    name: "UV Sterilization"
     icon: "mdi:solar-power"
     icon: "mdi:solar-power"
     dps:
     dps:
       - id: 10
       - id: 10
         name: switch
         name: switch
         type: boolean
         type: boolean
   - entity: light
   - entity: light
-    name: "Humidity Indicator"
+    name: Display
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean

+ 1 - 1
custom_components/tuya_local/devices/electriq_cd25pro_dehumidifier.yaml

@@ -64,7 +64,7 @@ secondary_entities:
           - dps_val: "0_90"
           - dps_val: "0_90"
             value: "Oscillate"
             value: "Oscillate"
   - entity: light
   - entity: light
-    name: "UV light"
+    name: "UV Sterilization"
     icon: "mdi:solar-power"    
     icon: "mdi:solar-power"    
     dps:
     dps:
       - id: 10
       - id: 10

+ 2 - 2
custom_components/tuya_local/devices/electriq_desd9lw_dehumidifier.yaml

@@ -106,14 +106,14 @@ primary_entity:
         max: 30
         max: 30
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
-    name: "UV sanitizer"
+    name: "UV Sterilization"
     icon: "mdi:solar-power"
     icon: "mdi:solar-power"
     dps:
     dps:
       - id: 15
       - id: 15
         name: switch
         name: switch
         type: boolean
         type: boolean
   - entity: switch
   - entity: switch
-    name: "Ioniser"
+    name: "Ionizer"
     icon: "mdi:atom-variant"
     icon: "mdi:atom-variant"
     dps:
     dps:
       - id: 12
       - id: 12

+ 1 - 1
custom_components/tuya_local/devices/goldair_dehumidifier.yaml

@@ -224,7 +224,7 @@ secondary_entities:
             icon_priority: 2
             icon_priority: 2
             readonly: true
             readonly: true
   - entity: light
   - entity: light
-    name: Panel Light
+    name: Display
     dps:
     dps:
       - id: 102
       - id: 102
         type: boolean
         type: boolean

+ 1 - 1
custom_components/tuya_local/devices/goldair_fan.yaml

@@ -121,7 +121,7 @@ secondary_entities:
         type: string
         type: string
         name: timer
         name: timer
   - entity: light
   - entity: light
-    name: Panel Light
+    name: Display
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean

+ 1 - 1
custom_components/tuya_local/devices/goldair_gpph_heater.yaml

@@ -120,7 +120,7 @@ primary_entity:
       name: eco_temperature
       name: eco_temperature
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
-    name: Panel Light
+    name: Display
     dps:
     dps:
       - id: 104
       - id: 104
         type: boolean
         type: boolean

+ 1 - 1
custom_components/tuya_local/devices/purline_m100_heater.yaml

@@ -65,7 +65,7 @@ primary_entity:
           value: "vertical"
           value: "vertical"
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
-    name: Panel Light
+    name: Display
     dps:
     dps:
       - id: 10
       - id: 10
         type: boolean
         type: boolean

+ 2 - 2
custom_components/tuya_local/devices/renpho_rp_ap001s.yaml

@@ -51,7 +51,7 @@ secondary_entities:
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
   - entity: light
   - entity: light
-    name: AQI mode
+    name: AQ indicator
     class: switch
     class: switch
     dps:
     dps:
       - id: 8
       - id: 8
@@ -63,7 +63,7 @@ secondary_entities:
           - dps_val: false
           - dps_val: false
             icon: "mdi:led-off"
             icon: "mdi:led-off"
   - entity: switch
   - entity: switch
-    name: Sleep Mode
+    name: Sleep
     class: switch
     class: switch
     icon: "mdi:power-sleep"
     icon: "mdi:power-sleep"
     dps:
     dps:

+ 5 - 0
custom_components/tuya_local/helpers/device_config.py

@@ -7,6 +7,7 @@ from os import walk
 from os.path import join, dirname, splitext, exists
 from os.path import join, dirname, splitext, exists
 from pydoc import locate
 from pydoc import locate
 
 
+from homeassistant.util import slugify
 from homeassistant.util.yaml import load_yaml
 from homeassistant.util.yaml import load_yaml
 
 
 import custom_components.tuya_local.devices as config_dir
 import custom_components.tuya_local.devices as config_dir
@@ -183,6 +184,10 @@ class TuyaEntityConfig:
     @property
     @property
     def config_id(self):
     def config_id(self):
         """The identifier for this entitiy in the config."""
         """The identifier for this entitiy in the config."""
+        own_name = self._config.get("name")
+        if own_name:
+            return f"{self.entity}_{slugify(own_name)}"
+
         return self.entity
         return self.entity
 
 
     @property
     @property

+ 15 - 1
custom_components/tuya_local/translations/en.json

@@ -28,7 +28,21 @@
 		    "climate": "Include a climate entity",
 		    "climate": "Include a climate entity",
 		    "light": "Include a light entity",
 		    "light": "Include a light entity",
 		    "lock": "Include a lock entity",
 		    "lock": "Include a lock entity",
-		    "switch": "Include a switch entity"
+		    "switch": "Include a switch entity",
+		    "fan_fan": "Include a fan entity",
+		    "fan_intensity": "Include intensity as a fan entitiy",
+		    "humidifier_humidifier": "Include a humidifier entity",
+		    "light_aq_indicator": "Include AQ indicator as a light entity",
+		    "light_display": "Include display as a light entity",
+		    "light_light": "Include a light entity",
+		    "light_uv_sterilization": "Include UV sterilization as a light entitiy",
+		    "lock_child_lock": "Include child lock as a lock entity",
+		    "switch_air_clean": "Include air clean as a switch entity",
+		    "switch_ionizer": "Include ionizer as a switch entity",
+		    "switch_master": "Include master switch as a switch entity",
+		    "switch_open_window_detector": "Include open window detect as a switch entity"
+		    "switch_sleep": "Include sleep mode as a switch entity",
+		    "switch_sound": "Include sound mute as a switch entity"
 		}
 		}
 	}
 	}
     },
     },

+ 9 - 2
tests/test_climate.py

@@ -38,7 +38,11 @@ async def test_init_entry_as_secondary(hass):
     """Test initialisation when fan is a secondary entity"""
     """Test initialisation when fan is a secondary entity"""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "dehumidifier", CONF_DEVICE_ID: "dummy", CONF_CLIMATE: True},
+        data={
+            CONF_TYPE: "goldair_dehumidifier",
+            CONF_DEVICE_ID: "dummy",
+            "climate_dehumidifier_as_climate": True,
+        },
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -51,7 +55,10 @@ async def test_init_entry_as_secondary(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
 
     await async_setup_entry(hass, entry, m_add_entities)
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_CLIMATE]) == GoldairDehumidifier
+    assert (
+        type(hass.data[DOMAIN]["dummy"]["climate_dehumidifier_as_climate"])
+        == GoldairDehumidifier
+    )
     m_add_entities.assert_called_once()
     m_add_entities.assert_called_once()
 
 
 
 

+ 11 - 7
tests/test_config_flow.py

@@ -53,7 +53,7 @@ async def test_init_entry(hass):
             CONF_LOCAL_KEY: "localkey",
             CONF_LOCAL_KEY: "localkey",
             CONF_TYPE: "kogan_kahtp_heater",
             CONF_TYPE: "kogan_kahtp_heater",
             CONF_CLIMATE: True,
             CONF_CLIMATE: True,
-            CONF_LOCK: True,
+            "lock_child_lock": True,
         },
         },
     )
     )
     entry.add_to_hass(hass)
     entry.add_to_hass(hass)
@@ -67,7 +67,7 @@ async def test_init_entry(hass):
 async def test_migrate_entry(mock_setup, hass):
 async def test_migrate_entry(mock_setup, hass):
     """Test migration from old entry format."""
     """Test migration from old entry format."""
     mock_device = MagicMock()
     mock_device = MagicMock()
-    mock_device.async_inferred_type = AsyncMock(return_value="heater")
+    mock_device.async_inferred_type = AsyncMock(return_value="goldair_gpph_heater")
     mock_setup.return_value = mock_device
     mock_setup.return_value = mock_device
 
 
     entry = MockConfigEntry(
     entry = MockConfigEntry(
@@ -308,7 +308,11 @@ async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
         )
         )
         result = await hass.config_entries.flow.async_configure(
         result = await hass.config_entries.flow.async_configure(
             flow["flow_id"],
             flow["flow_id"],
-            user_input={CONF_NAME: "test", CONF_CLIMATE: True, CONF_LOCK: False},
+            user_input={
+                CONF_NAME: "test",
+                CONF_CLIMATE: True,
+                "lock_child_lock": False,
+            },
         )
         )
         expected = {
         expected = {
             "version": 4,
             "version": 4,
@@ -325,7 +329,7 @@ async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
                 CONF_DEVICE_ID: "deviceid",
                 CONF_DEVICE_ID: "deviceid",
                 CONF_HOST: "hostname",
                 CONF_HOST: "hostname",
                 CONF_LOCAL_KEY: "localkey",
                 CONF_LOCAL_KEY: "localkey",
-                CONF_LOCK: False,
+                "lock_child_lock": False,
                 CONF_TYPE: "kogan_kahtp_heater",
                 CONF_TYPE: "kogan_kahtp_heater",
             },
             },
         }
         }
@@ -380,7 +384,7 @@ async def test_options_flow_modifies_config(mock_test, hass):
             CONF_DEVICE_ID: "deviceid",
             CONF_DEVICE_ID: "deviceid",
             CONF_HOST: "hostname",
             CONF_HOST: "hostname",
             CONF_LOCAL_KEY: "localkey",
             CONF_LOCAL_KEY: "localkey",
-            CONF_LOCK: True,
+            "lock_child_lock": True,
             CONF_NAME: "test",
             CONF_NAME: "test",
             CONF_TYPE: "kogan_kahtp_heater",
             CONF_TYPE: "kogan_kahtp_heater",
         },
         },
@@ -398,14 +402,14 @@ async def test_options_flow_modifies_config(mock_test, hass):
             CONF_CLIMATE: True,
             CONF_CLIMATE: True,
             CONF_HOST: "new_hostname",
             CONF_HOST: "new_hostname",
             CONF_LOCAL_KEY: "new_key",
             CONF_LOCAL_KEY: "new_key",
-            CONF_LOCK: False,
+            "lock_child_lock": False,
         },
         },
     )
     )
     expected = {
     expected = {
         CONF_CLIMATE: True,
         CONF_CLIMATE: True,
         CONF_HOST: "new_hostname",
         CONF_HOST: "new_hostname",
         CONF_LOCAL_KEY: "new_key",
         CONF_LOCAL_KEY: "new_key",
-        CONF_LOCK: False,
+        "lock_child_lock": False,
     }
     }
     assert "create_entry" == result["type"]
     assert "create_entry" == result["type"]
     assert "" == result["title"]
     assert "" == result["title"]

+ 7 - 3
tests/test_fan.py

@@ -16,7 +16,7 @@ async def test_init_entry(hass):
     """Test the initialisation."""
     """Test the initialisation."""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "fan", CONF_DEVICE_ID: "dummy", CONF_FAN: True},
+        data={CONF_TYPE: "goldair_fan", CONF_DEVICE_ID: "dummy", CONF_FAN: True},
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -37,7 +37,11 @@ async def test_init_entry_as_secondary(hass):
     """Test initialisation when fan is a secondary entity"""
     """Test initialisation when fan is a secondary entity"""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "dehumidifier", CONF_DEVICE_ID: "dummy", CONF_FAN: True},
+        data={
+            CONF_TYPE: "goldair_dehumidifier",
+            CONF_DEVICE_ID: "dummy",
+            "fan_fan": True,
+        },
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -50,7 +54,7 @@ async def test_init_entry_as_secondary(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
 
     await async_setup_entry(hass, entry, m_add_entities)
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_FAN]) == TuyaLocalFan
+    assert type(hass.data[DOMAIN]["dummy"]["fan_fan"]) == TuyaLocalFan
     m_add_entities.assert_called_once()
     m_add_entities.assert_called_once()
 
 
 
 

+ 6 - 2
tests/test_light.py

@@ -16,7 +16,11 @@ async def test_init_entry(hass):
     """Test the initialisation."""
     """Test the initialisation."""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "heater", CONF_DEVICE_ID: "dummy", CONF_LIGHT: True},
+        data={
+            CONF_TYPE: "goldair_gpph_heater",
+            CONF_DEVICE_ID: "dummy",
+            "light_panel_light": True,
+        },
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -29,7 +33,7 @@ async def test_init_entry(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
 
     await async_setup_entry(hass, entry, m_add_entities)
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_LIGHT]) == TuyaLocalLight
+    assert type(hass.data[DOMAIN]["dummy"]["light_panel_light"]) == TuyaLocalLight
     m_add_entities.assert_called_once()
     m_add_entities.assert_called_once()
 
 
 
 

+ 6 - 2
tests/test_lock.py

@@ -16,7 +16,11 @@ async def test_init_entry(hass):
     """Test the initialisation."""
     """Test the initialisation."""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "heater", CONF_DEVICE_ID: "dummy", CONF_LOCK: True},
+        data={
+            CONF_TYPE: "goldair_gpph_heater",
+            CONF_DEVICE_ID: "dummy",
+            "lock_child_lock": True,
+        },
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -29,7 +33,7 @@ async def test_init_entry(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
 
     await async_setup_entry(hass, entry, m_add_entities)
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_LOCK]) == TuyaLocalLock
+    assert type(hass.data[DOMAIN]["dummy"]["lock_child_lock"]) == TuyaLocalLock
     m_add_entities.assert_called_once()
     m_add_entities.assert_called_once()
 
 
 
 

+ 2 - 2
tests/test_switch.py

@@ -37,7 +37,7 @@ async def test_init_entry_as_secondary(hass):
     """Test the initialisation."""
     """Test the initialisation."""
     entry = MockConfigEntry(
     entry = MockConfigEntry(
         domain=DOMAIN,
         domain=DOMAIN,
-        data={CONF_TYPE: "deta_fan", CONF_DEVICE_ID: "dummy", CONF_SWITCH: True},
+        data={CONF_TYPE: "deta_fan", CONF_DEVICE_ID: "dummy", "switch_master": True},
     )
     )
     # although async, the async_add_entities function passed to
     # although async, the async_add_entities function passed to
     # async_setup_entry is called truly asynchronously. If we use
     # async_setup_entry is called truly asynchronously. If we use
@@ -50,7 +50,7 @@ async def test_init_entry_as_secondary(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
 
     await async_setup_entry(hass, entry, m_add_entities)
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_SWITCH]) == TuyaLocalSwitch
+    assert type(hass.data[DOMAIN]["dummy"]["switch_master"]) == TuyaLocalSwitch
     m_add_entities.assert_called_once()
     m_add_entities.assert_called_once()