Преглед изворни кода

Add support for Kogan Switches.

For Kogan SmartPlug with Energy Monitoring (single plug, piggyback style)
Supports:
   switch (on/off) - standard switch functionality
   current power drain monitoring (W) - standard switch functionality
   current current draw monitoring (A) - extra attribute
   current voltage monitoring (V) - extra attribute
   timer countdown monitoring (seconds) - extra attribute
Jason Rumney пре 5 година
родитељ
комит
f1024df385

+ 18 - 4
README.md

@@ -59,6 +59,16 @@ Current temperature is also displayed.
 
 
 - **Child lock** (on/off)
 - **Child lock** (on/off)
 
 
+### Switch devices
+
+**Kogan Energy monitoring Smart Plug
+- **power** (on/off)
+- **current power consumption** (Watts)
+- **Additional non-standard attributes**
+  - **current current draw** (Amps)
+  - **current voltage** (Volts)
+  - **timer** (seconds) [provided as read only]
+
 ---
 ---
 
 
 ### Warning
 ### Warning
@@ -114,28 +124,32 @@ tuya_local:
 
 
 #### type
 #### type
 
 
-    _(string) (Optional)_ The type of Tuya device. `auto` to automatically detect the device type, or if that doesn't work, select from the available options `heater`, `geco_heater` `gpcv_heater`, `dehumidifier`, `fan` or `kogan_heater`.
+    _(string) (Optional)_ The type of Tuya device. `auto` to automatically detect the device type, or if that doesn't work, select from the available options `heater`, `geco_heater` `gpcv_heater`, `dehumidifier`, `fan`, `kogan_heater` or `kogan_switch`.
 
 
     _Default value: auto_
     _Default value: auto_
 
 
 #### climate
 #### climate
 
 
-    _(boolean) (Optional)_ Whether to surface this appliance as a climate device.
+    _(boolean) (Optional)_ Whether to surface this appliance as a climate device. (not supported for switches)
 
 
     _Default value: true_
     _Default value: true_
 
 
 #### display_light
 #### display_light
 
 
-    _(boolean) (Optional)_ Whether to surface this appliance's LED display control as a light (not supported for Kogan, GECO or GPCV Heaters).
+    _(boolean) (Optional)_ Whether to surface this appliance's LED display control as a light (not supported for Kogan, GECO or GPCV Heaters, or switches).
 
 
     _Default value: false_
     _Default value: false_
 
 
 #### child_lock
 #### child_lock
 
 
-    _(boolean) (Optional)_ Whether to surface this appliances's child lock as a lock device (not supported for fans).
+    _(boolean) (Optional)_ Whether to surface this appliances's child lock as a lock device (not supported for fans or switches).
 
 
     _Default value: false_
     _Default value: false_
 
 
+#### switch
+
+    _(boolean) (Optional)_ Whether to surface this device as a switch device (supported only for switches)
+
 ## Heater gotchas
 ## Heater gotchas
 
 
 Goldair GPPH heaters have individual target temperatures for their Comfort and Eco modes, whereas Home Assistant only supports a single target temperature. Therefore, when you're in Comfort mode you will set the Comfort temperature (`5`-`35`), and when you're in Eco mode you will set the Eco temperature (`5`-`21`), just like you were using the heater's own control panel. Bear this in mind when writing automations that change the operation mode and set a temperature at the same time: you must change the operation mode _before_ setting the new target temperature, otherwise you will set the current thermostat rather than the new one.
 Goldair GPPH heaters have individual target temperatures for their Comfort and Eco modes, whereas Home Assistant only supports a single target temperature. Therefore, when you're in Comfort mode you will set the Comfort temperature (`5`-`35`), and when you're in Eco mode you will set the Eco temperature (`5`-`21`), just like you were using the heater's own control panel. Bear this in mind when writing automations that change the operation mode and set a temperature at the same time: you must change the operation mode _before_ setting the new target temperature, otherwise you will set the current thermostat rather than the new one.

+ 0 - 1
custom_components/tuya_local/climate.py

@@ -11,7 +11,6 @@ from .const import (
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
     CONF_TYPE_KOGAN_HEATER,
-    CONF_TYPE_GPPH_HEATER,
     CONF_CLIMATE,
     CONF_CLIMATE,
     CONF_TYPE_AUTO,
     CONF_TYPE_AUTO,
 )
 )

+ 8 - 0
custom_components/tuya_local/configuration.py

@@ -11,9 +11,11 @@ from .const import (
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_KOGAN_HEATER,
     CONF_TYPE_KOGAN_HEATER,
+    CONF_TYPE_KOGAN_SWITCH,
     CONF_CLIMATE,
     CONF_CLIMATE,
     CONF_DISPLAY_LIGHT,
     CONF_DISPLAY_LIGHT,
     CONF_CHILD_LOCK,
     CONF_CHILD_LOCK,
+    CONF_SWITCH,
     CONF_TYPE_AUTO,
     CONF_TYPE_AUTO,
 )
 )
 
 
@@ -60,6 +62,12 @@ INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
         "default": False,
         "default": False,
         "option": True,
         "option": True,
     },
     },
+    {
+        "key": CONF_SWITCH,
+        "type": bool,
+        "required": False,
+        "default": False,
+        "option": True,
 ]
 ]
 
 
 
 

+ 2 - 0
custom_components/tuya_local/const.py

@@ -12,9 +12,11 @@ CONF_TYPE_FAN = "fan"
 CONF_TYPE_GECO_HEATER = "geco_heater"
 CONF_TYPE_GECO_HEATER = "geco_heater"
 CONF_TYPE_GPCV_HEATER = "gpcv_heater"
 CONF_TYPE_GPCV_HEATER = "gpcv_heater"
 CONF_TYPE_KOGAN_HEATER = "kogan_heater"
 CONF_TYPE_KOGAN_HEATER = "kogan_heater"
+CONF_TYPE_KOGAN_SWITCH = "kogan_switch"
 CONF_CLIMATE = "climate"
 CONF_CLIMATE = "climate"
 CONF_DISPLAY_LIGHT = "display_light"
 CONF_DISPLAY_LIGHT = "display_light"
 CONF_CHILD_LOCK = "child_lock"
 CONF_CHILD_LOCK = "child_lock"
+CONF_SWITCH = "switch"
 
 
 API_PROTOCOL_VERSIONS = [3.3, 3.1]
 API_PROTOCOL_VERSIONS = [3.3, 3.1]
 SCAN_INTERVAL = timedelta(seconds=30)
 SCAN_INTERVAL = timedelta(seconds=30)

+ 4 - 2
custom_components/tuya_local/device.py

@@ -19,6 +19,7 @@ from .const import (
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_GPCV_HEATER,
     CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
     CONF_TYPE_KOGAN_HEATER,
+    CONF_TYPE_KOGAN_SWITCH,
 )
 )
 
 
 _LOGGER = logging.getLogger(__name__)
 _LOGGER = logging.getLogger(__name__)
@@ -89,17 +90,18 @@ class TuyaLocalDevice(object):
         _LOGGER.debug(f"Inferring device type from cached state: {cached_state}")
         _LOGGER.debug(f"Inferring device type from cached state: {cached_state}")
         if "1" not in cached_state:
         if "1" not in cached_state:
             return CONF_TYPE_KOGAN_HEATER
             return CONF_TYPE_KOGAN_HEATER
-        if "5" in cached_state and "3" not in cached_state:
+        if "5" in cached_state and "3" not in cached_state and "103" in cached_state:
             return CONF_TYPE_DEHUMIDIFIER
             return CONF_TYPE_DEHUMIDIFIER
         if "8" in cached_state:
         if "8" in cached_state:
             return CONF_TYPE_FAN
             return CONF_TYPE_FAN
+        if "5" in cached_state and "3" not in cached_state:
+            return CONF_TYPE_KOGAN_SWITCH
         if "106" in cached_state:
         if "106" in cached_state:
             return CONF_TYPE_GPPH_HEATER
             return CONF_TYPE_GPPH_HEATER
         if "7" in cached_state:
         if "7" in cached_state:
             return CONF_TYPE_GPCV_HEATER
             return CONF_TYPE_GPCV_HEATER
         if "3" in cached_state:
         if "3" in cached_state:
             return CONF_TYPE_GECO_HEATER
             return CONF_TYPE_GECO_HEATER
-
         return None
         return None
 
 
     def set_fixed_properties(self, fixed_properties):
     def set_fixed_properties(self, fixed_properties):

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


+ 16 - 0
custom_components/tuya_local/kogan_socket/const.py

@@ -0,0 +1,16 @@
+from homeassistant.components.switch import (
+    ATTR_CURRENT_POWER_W,
+    DEVICE_CLASS_OUTLET,
+)
+ATTR_SWITCH = "switch"
+ATTR_TIMER = "timer"
+ATTR_CURRENT_A = "current"
+ATTR_VOLTAGE_V = "voltage"
+
+PROPERTY_TO_DPS_ID = {
+    ATTR_SWITCH: "1",
+    ATTR_TIMER: "2",
+    ATTR_CURRENT_A: "4",
+    ATTR_CURRENT_POWER_W: "5",
+    ATTR_VOLTAGE_V: "6",
+}

+ 94 - 0
custom_components/tuya_local/kogan_socket/switch.py

@@ -0,0 +1,94 @@
+"""
+Platform to control the switch on Kogan WiFi-connected energy monitoring sockets.
+"""
+try:
+    from homeassistant.components.switch import SwitchEntity
+except ImportError:
+    from homeassistant.components.switch import SwitchDevice as SwitchEntity
+
+from homeassistant.components.switch import (
+    ATTR_CURRENT_POWER_W,
+    DEVICE_CLASS_OUTLET,
+)
+
+from homeassistant.const import STATE_UNAVAILABLE
+
+from .const import (
+    ATTR_CURRENT_A,
+    ATTR_SWITCH,
+    ATTR_TIMER,
+    ATTR_VOLTAGE_V,
+    PROPERTY_TO_DPS_ID,
+)
+
+
+class KoganSocketSwitch(SwitchEntity):
+    """Representation of a Kogan WiFi-connected energy monitoring socket"""
+
+    def __init__(self, device):
+        """Initialize the switch.
+        Args:
+            device (TuyaLocalDevice): The device API instance."""
+        self._device = device
+
+    @property
+    def should_poll(self):
+        """Return the polling state."""
+        return True
+
+    @property
+    def name(self):
+        """Return the name of the switch."""
+        return self._device.name
+
+    @property
+    def unique_id(self):
+        """Return the unique id for this switch."""
+        return self._device.unique_id
+
+    @property
+    def device_info(self):
+        """Return device information about this switch."""
+        return self._device.device_info
+
+    @property
+    def device_class(self):
+        """Return the class of this device"""
+        return DEVICE_CLASS_OUTLET
+
+    @property
+    def is_on(self):
+        """Return the whether the switch is on."""
+        if self.is_switched_on is None:
+            return STATE_UNAVAILABLE
+        else:
+            return self.is_switched_on
+
+    @property
+    def current_power_w(self):
+        """Return the current power consumption in Watts"""
+        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_CURRENT_POWER_W]) / 10.0
+
+    @property
+    def device_state_attributes(self):
+        """Get additional attributes that HA doesn't naturally support."""
+        timer = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TIMER])
+        voltage = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_VOLTAGE_V]) / 10.0
+        current = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_CURRENT_A]) / 1000.0
+        return {
+            ATTR_CURRENT_POWER_W: self.current_power_w,
+            ATTR_CURRENT_A: current,
+            ATTR_VOLTAGE_V: voltage,
+            ATTR_TIMER: timer,
+        }
+
+    async def async_turn_on(self, **kwargs):
+        """Turn the switch on"""
+        await self._device.async_set_property(PROPERTY_TO_DPS_ID[ATTR_SWITCH], True)
+
+    async def async_turn_off(self, **kwargs):
+        """Turn the switch off"""
+        await self._device.async_set_property(PROPERTY_TO_DPS_ID[ATTR_SWITCH], False)
+
+    async def async_update(self):
+        await self._device.async_refresh()

+ 39 - 0
custom_components/tuya_local/switch.py

@@ -0,0 +1,39 @@
+"""
+Setup for different kinds of Tuya switch devices
+"""
+from . import DOMAIN
+from .const import (
+    CONF_DEVICE_ID,
+    CONF_TYPE,
+    CONF_TYPE_KOGAN_SWITCH,
+    CONF_TYPE_AUTO,
+    CONF_SWITCH,
+)
+from .kogan_switch.switch import KoganSocketSwitch
+
+
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
+    """Set up the switch device according to its type."""
+    data = hass.data[DOMAIN][discovery_info[CONF_DEVICE_ID]]
+    device = data["device"]
+
+    if discovery_info[CONF_TYPE] == CONF_TYPE_AUTO:
+        discovery_info[CONF_TYPE] = await device.async_inferred_type()
+
+        if discovery_info[CONF_TYPE] is None:
+            raise ValueError(f"Unable to detecttype for device {device.name}")
+
+    if discovery_info[CONF_TYPE] == CONF_TYPE_KOGAN_SWITCH:
+        data[CONF_SWITCH] = KoganSocketSwitch(device)
+
+    if CONF_SWITCH in data:
+        async_add_entities([data[CONF_SWITCH]])
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    config = {**config_entry.data, **config_entry.options}
+    discovery_info = {
+        CONF_DEVICE_ID: config[CONF_DEVICE_ID],
+        CONF_TYPE: config[CONF_TYPE],
+    }
+    await async_setup_platform(hass, {}, async_add_entities, discovery_info)

+ 8 - 6
custom_components/tuya_local/translations/en.json

@@ -11,9 +11,10 @@
           "device_id": "Device ID (uuid)",
           "device_id": "Device ID (uuid)",
           "local_key": "Local key",
           "local_key": "Local key",
           "type": "Device type",
           "type": "Device type",
-          "climate": "Include a climate entity",
-          "display_light": "Include LED display as a light entity",
-          "child_lock": "Include child lock as a lock entity (unsupported on fans)"
+          "climate": "Include a climate entity (unsupported on switches)",
+          "display_light": "Include LED display as a light entity (Goldair only)",
+          "child_lock": "Include child lock as a lock entity (unsupported on fans and switches)",
+	  "switch": "Include a switch entity (switches only)"
         }
         }
       }
       }
     },
     },
@@ -34,9 +35,10 @@
           "host": "IP address or hostname",
           "host": "IP address or hostname",
           "local_key": "Local key",
           "local_key": "Local key",
           "type": "Device type",
           "type": "Device type",
-          "climate": "Include device as a climate entity",
-          "display_light": "Include LED display as light entity",
-          "child_lock": "Include child lock as lock entity (unsupported on fans)"
+          "climate": "Include device as a climate entity (unsupported for switches)",
+          "display_light": "Include LED display as light entity (Goldair only)",
+          "child_lock": "Include child lock as lock entity (unsupported on fans and switches)",
+	  "switch": "Include device as a switch entity (switches only)"
         }
         }
       },
       },
       "imported": {
       "imported": {

+ 1 - 1
hacs.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "Tuya local devices",
   "name": "Tuya local devices",
   "render_readme": true,
   "render_readme": true,
-  "domains": ["climate", "light", "lock"],
+    "domains": ["climate", "light", "lock", "switch"],
   "country": ["NZ", "AU"],
   "country": ["NZ", "AU"],
   "homeassistant": "0.109.0",
   "homeassistant": "0.109.0",
   "iot_class": "Local Polling"
   "iot_class": "Local Polling"