Pārlūkot izejas kodu

Convert light to generic component.

Jason Rumney 4 gadi atpakaļ
vecāks
revīzija
5698e1811d

+ 94 - 0
custom_components/tuya_local/generic/light.py

@@ -0,0 +1,94 @@
+"""
+Platform to control Tuya lights.
+Initially based on the secondary panel lighting control on some climate
+devices, so only providing simple on/off control.
+"""
+from homeassistant.components.light import LightEntity
+
+from ..device import TuyaLocalDevice
+from ..helpers.device_config import TuyaEntityConfig
+
+
+class TuyaLocalLight(LightEntity):
+    """Representation of a Tuya WiFi-connected light."""
+
+    def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
+        """
+        Initialize the light.
+        Args:
+            device (TuyaLocalDevice): The device API instance.
+            config (TuyaEntityConfig): The configuration for this entity.
+        """
+        self._device = device
+        self._config = config
+        self._attr_dps = []
+        for d in config.dps():
+            if d.name == "switch":
+                self._switch_dps = d
+            else:
+                self._attr_dps.append(d)
+
+    @property
+    def should_poll(self):
+        """Return the polling state."""
+        return True
+
+    @property
+    def name(self):
+        """Return the name of the light."""
+        return self._device.name
+
+    def friendly_name(self):
+        """Return the friendly name for this entity."""
+        return self._config.name
+
+    @property
+    def unique_id(self):
+        """Return the unique id for this heater LED display."""
+        return 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."""
+        if self.is_on:
+            return "mdi:led-on"
+        else:
+            return "mdi:led-off"
+
+    @property
+    def is_on(self):
+        """Return the current state."""
+        return self._switch_dps.map_from_dps(
+            self._device.get_property(self._switch_dps.id)
+        )
+
+    @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.map_from_dps(self._device.get_property(a.id))
+        return attr
+
+    async def async_turn_on(self):
+        await self._device.async_set_property(
+            self._switch_dps.id, self._switch_dps.map_to_dps(True)
+        )
+
+    async def async_turn_off(self):
+        await self._device.async_set_property(
+            self._switch_dps.id, self._switch_dps.map_to_dps(False)
+        )
+
+    async def async_toggle(self):
+        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()

+ 2 - 4
custom_components/tuya_local/light.py

@@ -10,6 +10,7 @@ from .const import (
     CONF_TYPE,
     CONF_TYPE_AUTO,
 )
+from .generic.light import TuyaLocalLight
 from .helpers.device_config import config_for_legacy_use
 
 _LOGGER = logging.getLogger(__name__)
@@ -35,10 +36,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         if ecfg.entity != "light":
             raise ValueError(f"{device.name} does not support use as a light device.")
 
-    legacy_class = ecfg.legacy_class
-    # Instantiate it: Sonarcloud thinks this is a blocker bug, and legacy_class
-    # is not callable, but the unit tests show the object is created...
-    data[CONF_DISPLAY_LIGHT] = legacy_class(device)
+    data[CONF_DISPLAY_LIGHT] = TuyaLocalLight(device, ecfg)
     async_add_entities([data[CONF_DISPLAY_LIGHT]])
     _LOGGER.debug(f"Adding light for {discovery_info[CONF_TYPE]}")
 

+ 1 - 1
custom_components/tuya_local/lock.py

@@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
 
 
 async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
-    """Set up the Goldair climate device according to its type."""
+    """Set up the lock device according to its type."""
     _LOGGER.debug(f"Domain data: {hass.data[DOMAIN]}")
     data = hass.data[DOMAIN][discovery_info[CONF_DEVICE_ID]]
     device = data["device"]

+ 94 - 7
tests/test_light.py

@@ -1,7 +1,7 @@
 """Tests for the light entity."""
-import pytest
 from pytest_homeassistant_custom_component.common import MockConfigEntry
-from unittest.mock import AsyncMock, Mock
+from unittest import IsolatedAsyncioTestCase
+from unittest.mock import AsyncMock, Mock, patch
 
 from custom_components.tuya_local.const import (
     CONF_DISPLAY_LIGHT,
@@ -11,9 +11,15 @@ from custom_components.tuya_local.const import (
     CONF_TYPE_GPPH_HEATER,
     DOMAIN,
 )
-from custom_components.tuya_local.heater.light import GoldairHeaterLedDisplayLight
+from custom_components.tuya_local.generic.light import TuyaLocalLight
+from custom_components.tuya_local.helpers.device_config import config_for_legacy_use
 from custom_components.tuya_local.light import async_setup_entry
 
+from .const import GPPH_HEATER_PAYLOAD
+from .helpers import assert_device_properties_set
+
+GPPH_LIGHTSWITCH_DPS = "104"
+
 
 async def test_init_entry(hass):
     """Test the initialisation."""
@@ -33,8 +39,89 @@ async def test_init_entry(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
     await async_setup_entry(hass, entry, m_add_entities)
-    assert (
-        type(hass.data[DOMAIN]["dummy"][CONF_DISPLAY_LIGHT])
-        == GoldairHeaterLedDisplayLight
-    )
+    assert type(hass.data[DOMAIN]["dummy"][CONF_DISPLAY_LIGHT]) == TuyaLocalLight
     m_add_entities.assert_called_once()
+
+
+class TestTuyaLocalLight(IsolatedAsyncioTestCase):
+    def setUp(self):
+        device_patcher = patch("custom_components.tuya_local.device.TuyaLocalDevice")
+        self.addCleanup(device_patcher.stop)
+        self.mock_device = device_patcher.start()
+        gpph_config = config_for_legacy_use(CONF_TYPE_GPPH_HEATER)
+        for light in gpph_config.secondary_entities():
+            if light.entity == "light":
+                break
+        self.subject = TuyaLocalLight(self.mock_device(), light)
+        self.dps = GPPH_HEATER_PAYLOAD.copy()
+        self.subject._device.get_property.side_effect = lambda id: self.dps[id]
+
+    def test_should_poll(self):
+        self.assertTrue(self.subject.should_poll)
+
+    def test_name_returns_device_name(self):
+        self.assertEqual(self.subject.name, self.subject._device.name)
+
+    def test_unique_id_returns_device_unique_id(self):
+        self.assertEqual(self.subject.unique_id, self.subject._device.unique_id)
+
+    def test_device_info_returns_device_info_from_device(self):
+        self.assertEqual(self.subject.device_info, self.subject._device.device_info)
+
+    def test_icon(self):
+        self.dps[GPPH_LIGHTSWITCH_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:led-on")
+
+        self.dps[GPPH_LIGHTSWITCH_DPS] = False
+        self.assertEqual(self.subject.icon, "mdi:led-off")
+
+    def test_is_on(self):
+        self.dps[GPPH_LIGHTSWITCH_DPS] = True
+        self.assertEqual(self.subject.is_on, True)
+
+        self.dps[GPPH_LIGHTSWITCH_DPS] = False
+        self.assertEqual(self.subject.is_on, False)
+
+    async def test_turn_on(self):
+        async with assert_device_properties_set(
+            self.subject._device, {GPPH_LIGHTSWITCH_DPS: True}
+        ):
+            await self.subject.async_turn_on()
+
+    async def test_turn_off(self):
+        async with assert_device_properties_set(
+            self.subject._device, {GPPH_LIGHTSWITCH_DPS: False}
+        ):
+            await self.subject.async_turn_off()
+
+    #    async def test_toggle_takes_no_action_when_heater_off(self):
+    #        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = False
+    #        await self.subject.async_toggle()
+    #        self.subject._device.async_set_property.assert_not_called
+
+    async def test_toggle_turns_the_light_on_when_it_was_off(self):
+        #        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = True
+        self.dps[GPPH_LIGHTSWITCH_DPS] = False
+
+        async with assert_device_properties_set(
+            self.subject._device, {GPPH_LIGHTSWITCH_DPS: True}
+        ):
+            await self.subject.async_toggle()
+
+    async def test_toggle_turns_the_light_off_when_it_was_on(self):
+        #        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = True
+        self.dps[GPPH_LIGHTSWITCH_DPS] = True
+
+        async with assert_device_properties_set(
+            self.subject._device, {GPPH_LIGHTSWITCH_DPS: False}
+        ):
+            await self.subject.async_toggle()
+
+    async def test_update(self):
+        result = AsyncMock()
+        self.subject._device.async_refresh.return_value = result()
+
+        await self.subject.async_update()
+
+        self.subject._device.async_refresh.assert_called_once()
+        result.assert_awaited()