Просмотр исходного кода

Refactor common platform functionality into a mixin

Reduce duplicate code, and make it easier to add new platforms.
Jason Rumney 4 лет назад
Родитель
Сommit
818c39d79a

+ 5 - 55
custom_components/tuya_local/generic/climate.py

@@ -21,7 +21,6 @@ from homeassistant.components.climate.const import (
     DEFAULT_MIN_HUMIDITY,
     DEFAULT_MIN_TEMP,
     HVAC_MODE_AUTO,
-    HVAC_MODE_OFF,
     SUPPORT_AUX_HEAT,
     SUPPORT_FAN_MODE,
     SUPPORT_PRESET_MODE,
@@ -40,11 +39,12 @@ from homeassistant.const import (
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 _LOGGER = logging.getLogger(__name__)
 
 
-class TuyaLocalClimate(ClimateEntity):
+class TuyaLocalClimate(TuyaLocalEntity, ClimateEntity):
     """Representation of a Tuya Climate entity."""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -54,11 +54,7 @@ class TuyaLocalClimate(ClimateEntity):
            device (TuyaLocalDevice): The device API instance.
            config (TuyaEntityConfig): The entity config.
         """
-        self._device = device
-        self._config = config
-        self._support_flags = 0
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
 
         self._aux_heat_dps = dps_map.pop(ATTR_AUX_HEAT, None)
         self._current_temperature_dps = dps_map.pop(ATTR_CURRENT_TEMPERATURE, None)
@@ -74,9 +70,8 @@ class TuyaLocalClimate(ClimateEntity):
         self._temp_low_dps = dps_map.pop(ATTR_TARGET_TEMP_LOW, None)
         self._unit_dps = dps_map.pop("temperature_unit", None)
 
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
+        self._init_end(dps_map)
+        self._support_flags = 0
 
         if self._aux_heat_dps:
             self._support_flags |= SUPPORT_AUX_HEAT
@@ -99,40 +94,6 @@ class TuyaLocalClimate(ClimateEntity):
         """Return the features supported by this climate device."""
         return self._support_flags
 
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the name of the climate entity for the UI."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this climate device."""
-        return self._config.unique_id(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."""
-        icon = self._config.icon(self._device)
-        if icon:
-            return icon
-        else:
-            return super().icon
-
     @property
     def temperature_unit(self):
         """Return the unit of measurement."""
@@ -392,14 +353,3 @@ class TuyaLocalClimate(ClimateEntity):
         if self._fan_mode_dps is None:
             raise NotImplementedError()
         await self._fan_mode_dps.async_set_value(self._device, fan_mode)
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 5 - 55
custom_components/tuya_local/generic/fan.py

@@ -13,11 +13,12 @@ from homeassistant.components.fan import (
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 _LOGGER = logging.getLogger(__name__)
 
 
-class TuyaLocalFan(FanEntity):
+class TuyaLocalFan(TuyaLocalEntity, FanEntity):
     """Representation of a Tuya Fan entity."""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -27,21 +28,15 @@ class TuyaLocalFan(FanEntity):
            device (TuyaLocalDevice): The device API instance.
            config (TuyaEntityConfig): The entity config.
         """
-        self._device = device
-        self._config = config
-        self._support_flags = 0
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._switch_dps = dps_map.pop("switch", None)
         self._preset_dps = dps_map.pop("preset_mode", None)
         self._speed_dps = dps_map.pop("speed", None)
         self._oscillate_dps = dps_map.pop("oscillate", None)
         self._direction_dps = dps_map.pop("direction", None)
+        self._init_end(dps_map)
 
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
+        self._support_flags = 0
         if self._preset_dps:
             self._support_flags |= SUPPORT_PRESET_MODE
         if self._speed_dps:
@@ -56,40 +51,6 @@ class TuyaLocalFan(FanEntity):
         """Return the features supported by this climate device."""
         return self._support_flags
 
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the friendly name of the entity for the UI."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this climate device."""
-        return self._config.unique_id(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."""
-        icon = self._config.icon(self._device)
-        if icon:
-            return icon
-        else:
-            return super().icon
-
     @property
     def is_on(self):
         """Return whether the switch is on or not."""
@@ -193,14 +154,3 @@ class TuyaLocalFan(FanEntity):
         if self._oscillate_dps is None:
             raise NotImplementedError()
         await self._oscillate_dps.async_set_value(self._device, oscillating)
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 5 - 54
custom_components/tuya_local/generic/humidifier.py

@@ -14,11 +14,12 @@ from homeassistant.components.humidifier.const import (
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 _LOGGER = logging.getLogger(__name__)
 
 
-class TuyaLocalHumidifier(HumidifierEntity):
+class TuyaLocalHumidifier(TuyaLocalEntity, HumidifierEntity):
     """Representation of a Tuya Humidifier entity."""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -28,18 +29,13 @@ class TuyaLocalHumidifier(HumidifierEntity):
            device (TuyaLocalDevice): The device API instance.
            config (TuyaEntityConfig): The entity config.
         """
-        self._device = device
-        self._config = config
-        self._support_flags = 0
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._humidity_dps = dps_map.pop("humidity", None)
         self._mode_dps = dps_map.pop("mode", None)
         self._switch_dps = dps_map.pop("switch", None)
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
+        self._init_end(dps_map)
 
+        self._support_flags = 0
         if self._mode_dps:
             self._support_flags |= SUPPORT_MODES
 
@@ -48,31 +44,6 @@ class TuyaLocalHumidifier(HumidifierEntity):
         """Return the features supported by this climate device."""
         return self._support_flags
 
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the friendly name of the entity for the UI."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this climate device."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about this heater."""
-        return self._device.device_info
-
     @property
     def device_class(self):
         """Return the class of this device"""
@@ -82,15 +53,6 @@ class TuyaLocalHumidifier(HumidifierEntity):
             else DEVICE_CLASS_HUMIDIFIER
         )
 
-    @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 is_on(self):
         """Return whether the switch is on or not."""
@@ -155,14 +117,3 @@ class TuyaLocalHumidifier(HumidifierEntity):
         if self._mode_dps is None:
             raise NotImplementedError()
         await self._mode_dps.async_set_value(self._device, mode)
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        await self._device.async_refresh()

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

@@ -15,9 +15,10 @@ from homeassistant.components.light import (
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 
-class TuyaLocalLight(LightEntity):
+class TuyaLocalLight(TuyaLocalEntity, LightEntity):
     """Representation of a Tuya WiFi-connected light."""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -27,51 +28,11 @@ class TuyaLocalLight(LightEntity):
             device (TuyaLocalDevice): The device API instance.
             config (TuyaEntityConfig): The configuration for this entity.
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._switch_dps = dps_map.pop("switch", None)
         self._brightness_dps = dps_map.pop("brightness", None)
         self._effect_dps = dps_map.pop("effect", None)
-
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the friendly name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this heater LED display."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about this heater LED display."""
-        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
+        self._init_end(dps_map)
 
     @property
     def supported_color_modes(self):
@@ -134,14 +95,6 @@ class TuyaLocalLight(LightEntity):
             return None
         return self._effect_dps.get_value(self._device)
 
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
     async def async_turn_on(self, **params):
         settings = {}
         if self._switch_dps:
@@ -180,6 +133,3 @@ class TuyaLocalLight(LightEntity):
         dps_display_on = self.is_on
 
         await (self.async_turn_on() if not dps_display_on else self.async_turn_off())
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 4 - 52
custom_components/tuya_local/generic/lock.py

@@ -9,9 +9,10 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 
-class TuyaLocalLock(LockEntity):
+class TuyaLocalLock(TuyaLocalEntity, LockEntity):
     """Representation of a Tuya Wi-Fi connected lock."""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -21,47 +22,9 @@ class TuyaLocalLock(LockEntity):
           device (TuyaLocalDevice): The device API instance.
           config (TuyaEntityConfig): The configuration for this entity.
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._lock_dps = dps_map.pop("lock")
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the friendly name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the device unique ID."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """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
+        self._init_end(dps_map)
 
     @property
     def state(self):
@@ -78,14 +41,6 @@ class TuyaLocalLock(LockEntity):
         """Return the a boolean representing whether the lock is locked."""
         return self.state == STATE_LOCKED
 
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
     async def async_lock(self, **kwargs):
         """Lock the lock."""
         await self._lock_dps.async_set_value(self._device, True)
@@ -93,6 +48,3 @@ class TuyaLocalLock(LockEntity):
     async def async_unlock(self, **kwargs):
         """Unlock the lock."""
         await self._lock_dps.async_set_value(self._device, False)
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 4 - 46
custom_components/tuya_local/generic/number.py

@@ -9,11 +9,12 @@ from homeassistant.components.number.const import (
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 MODE_AUTO = "auto"
 
 
-class TuyaLocalNumber(NumberEntity):
+class TuyaLocalNumber(TuyaLocalEntity, NumberEntity):
     """Representation of a Tuya Number"""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -23,43 +24,11 @@ class TuyaLocalNumber(NumberEntity):
             device (TuyaLocalDevice): the device API instance
             config (TuyaEntityConfig): the configuration for this entity
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._value_dps = dps_map.pop("value")
-
         if self._value_dps is None:
             raise AttributeError(f"{config.name} is missing a value dps")
-
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id of the device."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about this device."""
-        return self._device.device_info
+        self._init_end(dps_map)
 
     @property
     def min_value(self):
@@ -91,14 +60,3 @@ class TuyaLocalNumber(NumberEntity):
     async def async_set_value(self, value):
         """Set the number."""
         await self._value_dps.async_set_value(self._device, value)
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 4 - 47
custom_components/tuya_local/generic/select.py

@@ -5,9 +5,10 @@ from homeassistant.components.select import SelectEntity
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 
-class TuyaLocalSelect(SelectEntity):
+class TuyaLocalSelect(TuyaLocalEntity, SelectEntity):
     """Representation of a Tuya Select"""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -17,47 +18,15 @@ class TuyaLocalSelect(SelectEntity):
             device (TuyaLocalDevice): the device API instance
             config (TuyaEntityConfig): the configuration for this entity
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._option_dps = dps_map.pop("option")
-
         if self._option_dps is None:
             raise AttributeError(f"{config.name} is missing an option dps")
         if not self._option_dps.values(device):
             raise AttributeError(
                 f"{config.name} does not have a mapping to a list of options"
             )
-
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id of the device."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about this device."""
-        return self._device.device_info
+        self._init_end(dps_map)
 
     @property
     def options(self):
@@ -72,15 +41,3 @@ class TuyaLocalSelect(SelectEntity):
     async def async_select_option(self, option):
         "Set the option"
         await self._option_dps.async_set_value(self._device, option)
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        """Update the device state."""
-        await self._device.async_refresh()

+ 4 - 46
custom_components/tuya_local/generic/sensor.py

@@ -6,9 +6,10 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 
-class TuyaLocalSensor(SensorEntity):
+class TuyaLocalSensor(TuyaLocalEntity, SensorEntity):
     """Representation of a Tuya Sensor"""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -18,43 +19,11 @@ class TuyaLocalSensor(SensorEntity):
             device (TuyaLocalDevice): the device API instance.
             config (TuyaEntityConfig): the configuration for this entity
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._sensor_dps = dps_map.pop("sensor")
-
         if self._sensor_dps is None:
             raise AttributeError(f"{config.name} is missing a sensor dps")
-
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
-    @property
-    def name(self):
-        """Return the name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id of the device."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about the device."""
-        return self._device.device_info
+        self._init_end(dps_map)
 
     @property
     def device_class(self):
@@ -90,14 +59,3 @@ class TuyaLocalSensor(SensorEntity):
             unit = TEMP_FAHRENHEIT
 
         return unit
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that the integration itself does not support."""
-        attr = {}
-        for a in self._attr_dps:
-            attr[a.name] = a.get_value(self._device)
-        return attr
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 4 - 55
custom_components/tuya_local/generic/switch.py

@@ -5,7 +5,6 @@ heater open window detector toggle.
 """
 from homeassistant.components.switch import SwitchEntity
 from homeassistant.components.switch import (
-    ATTR_CURRENT_POWER_W,
     DEVICE_CLASS_OUTLET,
     DEVICE_CLASS_SWITCH,
 )
@@ -14,9 +13,10 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
+from ..helpers.mixin import TuyaLocalEntity
 
 
-class TuyaLocalSwitch(SwitchEntity):
+class TuyaLocalSwitch(TuyaLocalEntity, SwitchEntity):
     """Representation of a Tuya Switch"""
 
     def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
@@ -25,36 +25,10 @@ class TuyaLocalSwitch(SwitchEntity):
         Args:
             device (TuyaLocalDevice): The device API instance.
         """
-        self._device = device
-        self._config = config
-        self._attr_dps = []
-        dps_map = {c.name: c for c in config.dps()}
+        dps_map = self._init_begin(device, config)
         self._switch_dps = dps_map.pop("switch")
         self._power_dps = dps_map.get("current_power_w", None)
-
-        for d in dps_map.values():
-            if not d.hidden:
-                self._attr_dps.append(d)
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def name(self):
-        """Return the friendly name for this entity."""
-        return self._config.name(self._device.name)
-
-    @property
-    def unique_id(self):
-        """Return the unique id of the device."""
-        return self._config.unique_id(self._device.unique_id)
-
-    @property
-    def device_info(self):
-        """Return device information about the device."""
-        return self._device.device_info
+        self._init_end(dps_map)
 
     @property
     def device_class(self):
@@ -73,11 +47,6 @@ class TuyaLocalSwitch(SwitchEntity):
             return self.available
         return self._switch_dps.get_value(self._device)
 
-    @property
-    def available(self):
-        """Return whether the switch is available."""
-        return self._device.has_returned_state
-
     @property
     def current_power_w(self):
         """Return the current power consumption in Watts."""
@@ -90,23 +59,6 @@ class TuyaLocalSwitch(SwitchEntity):
 
         return pwr
 
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that HA doesn't naturally support."""
-        attr = {}
-        for a in self._attr_dps:
-            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)
@@ -114,6 +66,3 @@ class TuyaLocalSwitch(SwitchEntity):
     async def async_turn_off(self, **kwargs):
         """Turn the switch off"""
         await self._switch_dps.async_set_value(self._device, False)
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 64 - 0
custom_components/tuya_local/helpers/mixin.py

@@ -0,0 +1,64 @@
+"""
+Mixins to make writing new platforms easier
+"""
+import logging
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class TuyaLocalEntity:
+    """Common functions for all entity types."""
+
+    def _init_begin(self, device, config):
+        self._device = device
+        self._config = config
+        self._attr_dps = []
+        return {c.name: c for c in config.dps()}
+
+    def _init_end(self, dps):
+        for d in dps.values():
+            if not d.hidden:
+                self._attr_dps.append(d)
+
+    @property
+    def should_poll(self):
+        return True
+
+    @property
+    def available(self):
+        return self._device.has_returned_state
+
+    @property
+    def name(self):
+        """Return the name for the UI."""
+        return self._config.name(self._device.name)
+
+    @property
+    def unique_id(self):
+        """Return the unique id for this entity."""
+        return self._config.unique_id(self._device.unique_id)
+
+    @property
+    def device_info(self):
+        """Return the device's 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 device_state_attributes(self):
+        """Get additional attributes that the platform itself does not support."""
+        attr = {}
+        for a in self._attr_dps:
+            attr[a.name] = a.get_value(self._device)
+        return attr
+
+    async def async_update(self):
+        await self._device.async_refresh()