Explorar o código

Implement icon rules.

icon and icon_priority from config files are now used.
unskip all the tests that were waiting on this, and fix some config and tests.

Remove obsolete purline heater legacy implementation - the config was moved to generic a while ago, but legacy implementation was left behind.
Jason Rumney %!s(int64=4) %!d(string=hai) anos
pai
achega
9aded60e69

+ 5 - 0
custom_components/tuya_local/devices/eanons_humidifier.yaml

@@ -21,6 +21,11 @@ primary_entity:
     - id: 10
       name: switch
       type: boolean
+      mapping:
+        - dps_val: true
+          icon: "mdi:air-humidifier"
+        - dps_val: false
+          icon: "mdi:air-humidifier-off"
     - id: 12
       name: mode
       type: string

+ 4 - 3
custom_components/tuya_local/devices/goldair_fan.yaml

@@ -39,15 +39,16 @@ primary_entity:
 secondary_entities:
   - entity: climate
     legacy_class: ".legacy_fan.climate.GoldairFan"
-    icon: "mdi:fan"
     dps:
       - id: 1
         type: boolean
         mapping:
           - dps_val: false
             value: "off"
+            icon: "mdi:fan-off"
           - dps_val: true
             value: "fan_only"
+            icon: "mdi:fan"
         name: hvac_mode
       - id: 2
         type: integer
@@ -104,7 +105,7 @@ secondary_entities:
         type: boolean
         mapping:
           - dps_val: false
-            icon: "mdi:led-on"
-          - dps_val: true
             icon: "mdi:led-off"
+          - dps_val: true
+            icon: "mdi:led-on"
         name: switch

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

@@ -113,9 +113,9 @@ secondary_entities:
         type: boolean
         mapping:
           - dps_val: false
-            icon: "mdi:led-on"
-          - dps_val: true
             icon: "mdi:led-off"
+          - dps_val: true
+            icon: "mdi:led-on"
         name: switch
   - entity: lock
     name: Child Lock

+ 4 - 3
custom_components/tuya_local/generic/climate.py

@@ -119,10 +119,11 @@ class TuyaLocalClimate(ClimateEntity):
     @property
     def icon(self):
         """Return the icon to use in the frontend for this device."""
-        if self.hvac_mode == HVAC_MODE_OFF:
-            return "mdi:hvac-off"
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
         else:
-            return "mdi:hvac"
+            return super().icon
 
     @property
     def temperature_unit(self):

+ 4 - 3
custom_components/tuya_local/generic/fan.py

@@ -88,10 +88,11 @@ class TuyaLocalFan(FanEntity):
     @property
     def icon(self):
         """Return the icon to use in the frontend for this device."""
-        if self.is_on:
-            return "mdi:fan"
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
         else:
-            return "mdi:fan-off"
+            return super().icon
 
     @property
     def is_on(self):

+ 4 - 3
custom_components/tuya_local/generic/humidifier.py

@@ -88,10 +88,11 @@ class TuyaLocalHumidifier(HumidifierEntity):
     @property
     def icon(self):
         """Return the icon to use in the frontend for this device."""
-        if self.is_on:
-            return "mdi:air-humidifier"
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
         else:
-            return "mdi:air-humidifier-off"
+            return super().icon
 
     @property
     def is_on(self):

+ 4 - 3
custom_components/tuya_local/generic/light.py

@@ -56,10 +56,11 @@ class TuyaLocalLight(LightEntity):
     @property
     def icon(self):
         """Return the icon to use in the frontend for this device."""
-        if self.is_on:
-            return "mdi:led-on"
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
         else:
-            return "mdi:led-off"
+            return super().icon
 
     @property
     def is_on(self):

+ 9 - 0
custom_components/tuya_local/generic/lock.py

@@ -54,6 +54,15 @@ class TuyaLocalLock(LockEntity):
         """Return the device information."""
         return self._device.device_info
 
+    @property
+    def icon(self):
+        """Return the icon to use in the frontend for this device."""
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
+        else:
+            return super().icon
+
     @property
     def state(self):
         """Return the current state."""

+ 9 - 0
custom_components/tuya_local/generic/switch.py

@@ -100,6 +100,15 @@ class TuyaLocalSwitch(SwitchEntity):
             attr[a.name] = a.get_value(self._device)
         return attr
 
+    @property
+    def icon(self):
+        """Return the icon to use in the frontend for this device."""
+        icon = self._config.icon(self._device)
+        if icon:
+            return icon
+        else:
+            return super().icon
+
     async def async_turn_on(self, **kwargs):
         """Turn the switch on"""
         await self._switch_dps.async_set_value(self._device, True)

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

@@ -173,6 +173,18 @@ class TuyaEntityConfig:
         """The device class of this entity."""
         return self._config.get("class")
 
+    def icon(self, device):
+        """Return the icon for this device, with state as given."""
+        icon = self._config.get("icon", None)
+        priority = self._config.get("icon_priority", 100)
+
+        for d in self.dps():
+            rule = d.icon_rule(device)
+            if rule and rule["priority"] < priority:
+                icon = rule["icon"]
+                priority = rule["priority"]
+        return icon
+
     def dps(self):
         """Iterate through the list of dps for this entity."""
         for d in self._config["dps"]:
@@ -462,6 +474,20 @@ class TuyaDpsConfig:
         dps_map[self.id] = result
         return dps_map
 
+    def icon_rule(self, device):
+        mapping = self._find_map_for_dps(device.get_property(self.id))
+        icon = None
+        priority = 100
+        if mapping:
+            icon = mapping.get("icon", icon)
+            priority = mapping.get("icon_priority", 10 if icon else 100)
+            cond = self._active_condition(mapping, device)
+            if cond and cond.get("icon_priority", 10) < priority:
+                icon = cond.get("icon", icon)
+                priority = cond.get("icon_priority", 10 if icon else 100)
+
+        return {"priority": priority, "icon": icon}
+
 
 def available_configs():
     """List the available config files."""

+ 0 - 0
custom_components/tuya_local/purline_m100_heater/__init__.py


+ 0 - 222
custom_components/tuya_local/purline_m100_heater/climate.py

@@ -1,222 +0,0 @@
-"""
-Purline Hoti M100 WiFi Heater device.
-"""
-from homeassistant.components.climate import ClimateEntity
-from homeassistant.components.climate.const import (
-    ATTR_HVAC_MODE,
-    ATTR_SWING_MODE,
-    HVAC_MODE_FAN_ONLY,
-    HVAC_MODE_HEAT,
-    HVAC_MODE_OFF,
-    SUPPORT_PRESET_MODE,
-    SUPPORT_SWING_MODE,
-    SUPPORT_TARGET_TEMPERATURE,
-    SWING_OFF,
-    SWING_VERTICAL,
-)
-from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE
-
-from ..device import TuyaLocalDevice
-from .const import (
-    ATTR_POWER_LEVEL,
-    ATTR_TARGET_TEMPERATURE,
-    POWER_LEVEL_AUTO,
-    POWER_LEVEL_FANONLY,
-    POWER_LEVEL_TO_DPS_LEVEL,
-    PRESET_AUTO,
-    PRESET_FAN,
-    PROPERTY_TO_DPS_ID,
-)
-
-SUPPORT_FLAGS = SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE
-
-
-class PurlineM100Heater(ClimateEntity):
-    """Representation of a Purline Hoti M100 WiFi heater."""
-
-    def __init__(self, device):
-        """Initialize the heater.
-        Args:
-            device (TuyaLocalDevice): The device API instance."""
-        self._device = device
-
-        self._support_flags = SUPPORT_FLAGS
-
-        self._TEMPERATURE_STEP = 1
-        self._TEMPERATURE_LIMITS = {"min": 15, "max": 35}
-
-    @property
-    def supported_features(self):
-        """Return the list of supported features."""
-        return self._support_flags
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def name(self):
-        """Return the name of the climate device."""
-        return self._device.name
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this heater."""
-        return self._device.unique_id
-
-    @property
-    def device_info(self):
-        """Return device information about this heater."""
-        return self._device.device_info
-
-    @property
-    def icon(self):
-        """Return the icon to use in the frontend for this device."""
-        hvac_mode = self.hvac_mode
-
-        if hvac_mode == HVAC_MODE_HEAT:
-            return "mdi:radiator"
-        elif hvac_mode == HVAC_MODE_FAN_ONLY:
-            return "mdi:fan"
-        else:
-            return "mdi:radiator-disabled"
-
-    @property
-    def temperature_unit(self):
-        """Return the unit of measurement."""
-        return self._device.temperature_unit
-
-    @property
-    def target_temperature(self):
-        """Return the temperature we try to reach."""
-        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE])
-
-    @property
-    def target_temperature_step(self):
-        """Return the supported step of target temperature."""
-        return self._TEMPERATURE_STEP
-
-    @property
-    def min_temp(self):
-        """Return the minimum temperature."""
-        return self._TEMPERATURE_LIMITS["min"]
-
-    @property
-    def max_temp(self):
-        """Return the maximum temperature."""
-        return self._TEMPERATURE_LIMITS["max"]
-
-    async def async_set_temperature(self, **kwargs):
-        """Set new target temperatures."""
-        if kwargs.get(ATTR_TEMPERATURE) is not None:
-            await self.async_set_target_temperature(kwargs.get(ATTR_TEMPERATURE))
-
-    async def async_set_target_temperature(self, target_temperature):
-        target_temperature = int(round(target_temperature))
-
-        limits = self._TEMPERATURE_LIMITS
-        if not limits["min"] <= target_temperature <= limits["max"]:
-            raise ValueError(
-                f"Target temperature ({target_temperature}) must be between "
-                f'{limits["min"]} and {limits["max"]}.'
-            )
-
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE], target_temperature
-        )
-
-    @property
-    def current_temperature(self):
-        """Return the current temperature."""
-        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TEMPERATURE])
-
-    @property
-    def hvac_mode(self):
-        """Return current HVAC mode, ie Heat or Off."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
-        dps_level = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-        if dps_mode is not None:
-            if dps_mode is False:
-                return HVAC_MODE_OFF
-            elif dps_level == POWER_LEVEL_FANONLY:
-                return HVAC_MODE_FAN_ONLY
-            else:
-                return HVAC_MODE_HEAT
-        else:
-            return STATE_UNAVAILABLE
-
-    @property
-    def hvac_modes(self):
-        """Return the list of available HVAC modes."""
-        return [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT]
-
-    async def async_set_hvac_mode(self, hvac_mode):
-        """Set new HVAC mode."""
-        dps_mode = True
-        if hvac_mode == HVAC_MODE_OFF:
-            dps_mode = False
-
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode
-        )
-        dps_level = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-
-        if hvac_mode == HVAC_MODE_FAN_ONLY and dps_level != POWER_LEVEL_FANONLY:
-            await self.async_set_preset_mode(PRESET_FAN)
-        elif hvac_mode == HVAC_MODE_HEAT and dps_level == POWER_LEVEL_FANONLY:
-            await self.async_set_preset_mode(PRESET_AUTO)
-
-    @property
-    def preset_mode(self):
-        """Return the power level."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-        if dps_mode is None:
-            return None
-
-        return TuyaLocalDevice.get_key_for_value(POWER_LEVEL_TO_DPS_LEVEL, dps_mode)
-
-    @property
-    def preset_modes(self):
-        """Retrn the list of available preset modes."""
-        return list(POWER_LEVEL_TO_DPS_LEVEL.keys())
-
-    async def async_set_preset_mode(self, preset_mode):
-        """Set new power level."""
-        dps_mode = POWER_LEVEL_TO_DPS_LEVEL[preset_mode]
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL], dps_mode
-        )
-
-    @property
-    def swing_mode(self):
-        """Return the swing mode."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_SWING_MODE])
-        if dps_mode is None:
-            return None
-
-        if dps_mode:
-            return SWING_VERTICAL
-        else:
-            return SWING_OFF
-
-    @property
-    def swing_modes(self):
-        """List of swing modes."""
-        return [SWING_OFF, SWING_VERTICAL]
-
-    async def async_set_swing_mode(self, swing_mode):
-        """Set new swing mode."""
-        if swing_mode == SWING_VERTICAL:
-            swing_state = True
-        elif swing_mode == SWING_OFF:
-            swing_state = False
-        else:
-            raise ValueError(f"Invalid swing mode: {swing_mode}")
-
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_SWING_MODE], swing_state
-        )
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 0 - 39
custom_components/tuya_local/purline_m100_heater/const.py

@@ -1,39 +0,0 @@
-from homeassistant.components.climate.const import (
-    ATTR_HVAC_MODE,
-    ATTR_SWING_MODE,
-)
-from homeassistant.const import ATTR_TEMPERATURE
-
-ATTR_TARGET_TEMPERATURE = "target_temperature"
-ATTR_DISPLAY_OFF = "display_off"
-ATTR_POWER_LEVEL = "power_level"
-ATTR_TIMER_HR = "timer_hours"
-ATTR_TIMER_REMAIN = "timer_remain"
-ATTR_OPEN_WINDOW_DETECT = "open_window_detect"
-
-POWER_LEVEL_AUTO = "auto"
-POWER_LEVEL_FANONLY = "off"
-PRESET_FAN = "Fan"
-PRESET_AUTO = "Auto"
-
-POWER_LEVEL_TO_DPS_LEVEL = {
-    PRESET_FAN: POWER_LEVEL_FANONLY,
-    "1": "1",
-    "2": "2",
-    "3": "3",
-    "4": "4",
-    "5": "5",
-    PRESET_AUTO: POWER_LEVEL_AUTO,
-}
-
-PROPERTY_TO_DPS_ID = {
-    ATTR_HVAC_MODE: "1",
-    ATTR_TARGET_TEMPERATURE: "2",
-    ATTR_TEMPERATURE: "3",
-    ATTR_POWER_LEVEL: "5",
-    ATTR_DISPLAY_OFF: "10",
-    ATTR_TIMER_HR: "11",
-    ATTR_TIMER_REMAIN: "12",
-    ATTR_OPEN_WINDOW_DETECT: "101",
-    ATTR_SWING_MODE: "102",
-}