瀏覽代碼

Do not use STATE_UNAVAILABLE for is_on return value.

STATE_UNAVAILABLE appears true to boolean operations. Instead use None.
Instead, implement the available property.

Also more widely support devices with no power switch - consider these on if the device is available.

Issue #66
Jason Rumney 4 年之前
父節點
當前提交
42d214d016

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

@@ -104,6 +104,11 @@ class TuyaLocalClimate(ClimateEntity):
         """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."""

+ 7 - 11
custom_components/tuya_local/generic/fan.py

@@ -11,10 +11,6 @@ from homeassistant.components.fan import (
     SUPPORT_SET_SPEED,
 )
 
-from homeassistant.const import (
-    STATE_UNAVAILABLE,
-)
-
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
 
@@ -65,6 +61,11 @@ class TuyaLocalFan(FanEntity):
         """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."""
@@ -94,13 +95,8 @@ class TuyaLocalFan(FanEntity):
         """Return whether the switch is on or not."""
         # If there is no switch, it is always on
         if self._switch_dps is None:
-            return True
-        is_switched_on = self._switch_dps.get_value(self._device)
-
-        if is_switched_on is None:
-            return STATE_UNAVAILABLE
-        else:
-            return bool(is_switched_on)
+            return self.available
+        return self._switch_dps.get_value(self._device)
 
     async def async_turn_on(self, **kwargs):
         """Turn the switch on"""

+ 9 - 9
custom_components/tuya_local/generic/humidifier.py

@@ -11,9 +11,6 @@ from homeassistant.components.humidifier.const import (
     DEVICE_CLASS_HUMIDIFIER,
     SUPPORT_MODES,
 )
-from homeassistant.const import (
-    STATE_UNAVAILABLE,
-)
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
@@ -56,6 +53,11 @@ class TuyaLocalHumidifier(HumidifierEntity):
         """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."""
@@ -92,12 +94,10 @@ class TuyaLocalHumidifier(HumidifierEntity):
     @property
     def is_on(self):
         """Return whether the switch is on or not."""
-        is_switched_on = self._switch_dps.get_value(self._device)
-
-        if is_switched_on is None:
-            return STATE_UNAVAILABLE
-        else:
-            return is_switched_on
+        # If there is no switch, it is always on if available
+        if self._switch_dps is None:
+            return self.available
+        return self._switch_dps.get_value(self._device)
 
     async def async_turn_on(self, **kwargs):
         """Turn the switch on"""

+ 7 - 2
custom_components/tuya_local/generic/light.py

@@ -43,6 +43,11 @@ class TuyaLocalLight(LightEntity):
         """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."""
@@ -104,8 +109,8 @@ class TuyaLocalLight(LightEntity):
             b = self.brightness
             return isinstance(b, int) and b > 0
         else:
-            # There shouldn't be lights without control, but if there are, assume always on
-            return True
+            # There shouldn't be lights without control, but if there are, assume always on if they are responding
+            return self.available
 
     @property
     def brightness(self):

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

@@ -34,6 +34,11 @@ class TuyaLocalLock(LockEntity):
     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."""

+ 5 - 0
custom_components/tuya_local/generic/number.py

@@ -41,6 +41,11 @@ class TuyaLocalNumber(NumberEntity):
         """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."""

+ 5 - 0
custom_components/tuya_local/generic/select.py

@@ -39,6 +39,11 @@ class TuyaLocalSelect(SelectEntity):
         """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."""

+ 5 - 0
custom_components/tuya_local/generic/sensor.py

@@ -36,6 +36,11 @@ class TuyaLocalSensor(SensorEntity):
         """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."""

+ 8 - 5
custom_components/tuya_local/generic/switch.py

@@ -68,12 +68,15 @@ class TuyaLocalSwitch(SwitchEntity):
     @property
     def is_on(self):
         """Return whether the switch is on or not."""
-        is_switched_on = self._switch_dps.get_value(self._device)
+        # if there is no switch, it is always on if available.
+        if self._switch_dps is None:
+            return self.available
+        return self._switch_dps.get_value(self._device)
 
-        if is_switched_on is None:
-            return STATE_UNAVAILABLE
-        else:
-            return is_switched_on
+    @property
+    def available(self):
+        """Return whether the switch is available."""
+        return self._device.has_returned_state
 
     @property
     def current_power_w(self):

+ 5 - 0
tests/devices/base_device_tests.py

@@ -42,6 +42,7 @@ class TuyaDeviceTestCase(IsolatedAsyncioTestCase):
         self.mock_device.get_property.side_effect = lambda id: self.dps[id]
         cfg = TuyaDeviceConfig(config_file)
         self.conf_type = cfg.legacy_type
+        type(self.mock_device).has_returned_state = PropertyMock(return_value=True)
         type(self.mock_device).unique_id = PropertyMock(return_value=str(uuid4()))
         self.mock_device.name = cfg.name
 
@@ -73,6 +74,10 @@ class TuyaDeviceTestCase(IsolatedAsyncioTestCase):
         for e in self.entities.values():
             self.assertTrue(e.should_poll)
 
+    def test_available(self):
+        for e in self.entities.values():
+            self.assertTrue(e.available)
+
     def test_name_returns_device_name(self):
         for e in self.entities:
             self.assertEqual(self.entities[e].name, self.names[e])

+ 1 - 1
tests/devices/test_anko_fan.py

@@ -38,7 +38,7 @@ class TestAnkoFan(TuyaDeviceTestCase):
         self.assertFalse(self.subject.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 1
tests/devices/test_arlec_fan.py

@@ -41,7 +41,7 @@ class TestArlecFan(TuyaDeviceTestCase):
         self.assertFalse(self.subject.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 2 - 4
tests/devices/test_deta_fan.py

@@ -1,8 +1,6 @@
 from homeassistant.components.fan import SUPPORT_SET_SPEED
 from homeassistant.components.light import COLOR_MODE_ONOFF
 
-from homeassistant.const import STATE_UNAVAILABLE
-
 from ..const import DETA_FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from .base_device_tests import TuyaDeviceTestCase
@@ -38,7 +36,7 @@ class TestDetaFan(TuyaDeviceTestCase):
         self.assertFalse(self.subject.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(
@@ -111,7 +109,7 @@ class TestDetaFan(TuyaDeviceTestCase):
         self.assertFalse(self.switch.is_on)
 
         self.dps[MASTER_DPS] = None
-        self.assertEqual(self.switch.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.switch.is_on)
 
     async def test_switch_turn_on(self):
         async with assert_device_properties_set(

+ 3 - 3
tests/devices/test_eanons_humidifier.py

@@ -108,8 +108,8 @@ class TestEanonsHumidifier(TuyaDeviceTestCase):
         self.assertFalse(self.fan.is_on)
 
         self.dps[HVACMODE_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
-        self.assertEqual(self.fan.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
+        self.assertIsNone(self.fan.is_on)
 
     async def test_climate_turn_on(self):
         async with assert_device_properties_set(
@@ -344,7 +344,7 @@ class TestEanonsHumidifier(TuyaDeviceTestCase):
 
     def test_switch_is_on_when_unavailable(self):
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.switch.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.switch.is_on)
 
     async def test_switch_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 2
tests/devices/test_electriq_cd12_dehumidifier.py

@@ -1,6 +1,5 @@
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.light import COLOR_MODE_ONOFF
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import ELECTRIQ_CD12PW_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -79,7 +78,7 @@ class TestElectriqCD20ProDehumidifier(TuyaDeviceTestCase):
         self.assertFalse(self.subject.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 2 - 3
tests/devices/test_electriq_cd20_dehumidifier.py

@@ -1,7 +1,6 @@
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.light import COLOR_MODE_ONOFF
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import ELECTRIQ_CD20PRO_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -92,8 +91,8 @@ class TestElectriqCD20ProDehumidifier(TuyaDeviceTestCase):
         self.assertFalse(self.fan.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
-        self.assertEqual(self.fan.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
+        self.assertIsNone(self.fan.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 2 - 3
tests/devices/test_electriq_cd25_dehumidifier.py

@@ -1,7 +1,6 @@
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.light import COLOR_MODE_ONOFF
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import ELECTRIQ_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -90,8 +89,8 @@ class TestElectriqCD25ProDehumidifier(TuyaDeviceTestCase):
         self.assertFalse(self.fan.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
-        self.assertEqual(self.fan.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
+        self.assertIsNone(self.fan.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 1
tests/devices/test_goldair_fan.py

@@ -71,7 +71,7 @@ class TestGoldairFan(TuyaDeviceTestCase):
 
         self.dps[HVACMODE_DPS] = None
         self.assertEqual(self.climate.hvac_mode, STATE_UNAVAILABLE)
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     def test_climate_hvac_modes(self):
         self.assertCountEqual(

+ 3 - 4
tests/devices/test_grid_connect_double_power_point.py

@@ -1,6 +1,5 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import GRIDCONNECT_2SOCKET_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -47,8 +46,8 @@ class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
         self.dps[MASTER_DPS] = False
         self.assertFalse(self.subject.is_on)
 
-        self.assertEqual(self.switch1.is_on, STATE_UNAVAILABLE)
-        self.assertEqual(self.switch1.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.switch1.is_on)
+        self.assertIsNone(self.switch1.is_on)
 
         self.dps[MASTER_DPS] = True
         self.dps[SWITCH1_DPS] = True
@@ -63,7 +62,7 @@ class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
 
     def test_is_on_when_unavailable(self):
         self.dps[MASTER_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 2 - 3
tests/devices/test_kogan_dehumidifier.py

@@ -1,6 +1,5 @@
 from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED
 from homeassistant.components.humidifier import SUPPORT_MODES
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import KOGAN_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -69,8 +68,8 @@ class TestKoganDehumidifier(TuyaDeviceTestCase):
         self.assertFalse(self.fan.is_on)
 
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
-        self.assertEqual(self.fan.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
+        self.assertIsNone(self.fan.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 1
tests/devices/test_lexy_f501_fan.py

@@ -45,7 +45,7 @@ class TestLexyF501Fan(TuyaDeviceTestCase):
         self.assertFalse(self.subject.is_on)
 
         self.dps[POWER_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 3 - 3
tests/devices/test_purline_m100_heater.py

@@ -225,10 +225,10 @@ class TestPulineM100Heater(TuyaDeviceTestCase):
 
     def test_light_is_on(self):
         self.dps[LIGHTOFF_DPS] = False
-        self.assertEqual(self.light.is_on, True)
+        self.assertTrue(self.light.is_on)
 
         self.dps[LIGHTOFF_DPS] = True
-        self.assertEqual(self.light.is_on, False)
+        self.assertFalse(self.light.is_on)
 
     def test_light_state_attributes(self):
         self.assertEqual(self.light.device_state_attributes, {})
@@ -273,7 +273,7 @@ class TestPulineM100Heater(TuyaDeviceTestCase):
 
     def test_switch_is_on_when_unavailable(self):
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.switch.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.switch.is_on)
 
     async def test_switch_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 1
tests/devices/test_renpho_rp_ap001s.py

@@ -39,7 +39,7 @@ class TestRenphoPurifier(TuyaDeviceTestCase):
         self.dps[SWITCH_DPS] = False
         self.assertFalse(self.subject.is_on)
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 2
tests/devices/test_smartplugv1.py

@@ -1,6 +1,5 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import KOGAN_SOCKET_PAYLOAD
 from ..helpers import assert_device_properties_set
@@ -32,7 +31,7 @@ class TestKoganSwitch(TuyaDeviceTestCase):
 
     def test_is_on_when_unavailable(self):
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(

+ 1 - 2
tests/devices/test_smartplugv2.py

@@ -1,6 +1,5 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
-from homeassistant.const import STATE_UNAVAILABLE
 
 from ..const import KOGAN_SOCKET_PAYLOAD2
 from ..helpers import assert_device_properties_set
@@ -32,7 +31,7 @@ class TestSwitchV2(TuyaDeviceTestCase):
 
     def test_is_on_when_unavailable(self):
         self.dps[SWITCH_DPS] = None
-        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertIsNone(self.subject.is_on)
 
     async def test_turn_on(self):
         async with assert_device_properties_set(