Bläddra i källkod

Merge branch 'homeassistant-goldair-climate'

This is a merge of updated pull request nikrolls/homeassistant-goldair-climate #28
It adds support for GECO series heaters, which seem to be a reduced functionality version of the GPCV series (no High/Low selection).
Jason Rumney 6 år sedan
förälder
incheckning
fc4a24f21a

+ 14 - 3
README.md

@@ -20,6 +20,12 @@ Current temperature is also displayed.
 
 Current temperature is also displayed.
 
+**GECO Heaters**
+* **power** (on/off)
+* **target temperature** (`15`-`35` in °C)
+
+Current temperature is also displayed.
+
 **Goldair Dehumidifiers**
 * **power** (on/off)
 * **mode** (Normal, Low, High, Dry clothes, Air clean)
@@ -52,7 +58,12 @@ There was previously a sensor option, however this is easily achieved using a [t
 ---
 
 ### Warning
-Please note, this component has currently only been tested with the Goldair GPPH (inverter), GPDH420 (dehumidifier), and GCPF315 fan, however theoretically it should also work with GEPH and GPCV heater devices, may work with the GPDH440 dehumidifier and any other Goldair heaters, dehumidifiers or fans based on the Tuya platform.
+Please note, this component has currently only been tested with the Goldair GPPH (inverter), GPDH420 (dehumidifier), and GCPF315 fan, however theoretically it should also work with GECO/GEPH and GPCV heater devices, may work with the GPDH440 dehumidifier and any other Goldair heaters, dehumidifiers or fans based on the Tuya platform.
+
+GPCV support is based on feedback from etamtlosz on Issue #27
+GECO support is based on work in KiLLeRRaT/homeassistant-goldair-climate and the feature set from the online manual for these heaters. GEPH heaters appear to be the same as the GECO270, so may also work with this setting.  This heater is almost compatible with the GPCV but without the Low/High mode. 
+
+The models above are autodetected, so if your model is identical to one of the supported devices, it should just work. However if your model has additional features, then it may be misdetected as a different device by the simplistic detection algorithm, even though it is similar to one of the other devices.  In this case, try the type you think it is similar to manually, and feedback if it works so the auto detection can be improved.
 
 Kogan heater support is tested with the Kogan SmarterHome 1500W Smart Panel Heater.  If you have another type of Kogan SmarterHome heater, it may or may not work with the same configuration.
 
@@ -103,7 +114,7 @@ tuya_local:
                                               [as per the instructions below](#finding-your-device-id-and-local-key).
 
 #### 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`, `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` or `kogan_heater`.
 
     *Default value: auto*
 
@@ -113,7 +124,7 @@ tuya_local:
     *Default value: true* 
 
 #### display_light
-    *(boolean) (Optional)* Whether to surface this appliance's LED display control as a light (not supported for Kogan and GPCV Heaters).
+    *(boolean) (Optional)* Whether to surface this appliance's LED display control as a light (not supported for Kogan, GECO or GPCV Heaters).
 
     *Default value: false* 
 

+ 5 - 1
custom_components/tuya_local/climate.py

@@ -3,10 +3,12 @@ Setup for different kinds of Tuya climate devices
 """
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
-                    CONF_TYPE_FAN, CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
+                    CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER,
+                    CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
                     CONF_TYPE_KOGAN_HEATER, CONF_CLIMATE, CONF_TYPE_AUTO)
 from .dehumidifier.climate import GoldairDehumidifier
 from .fan.climate import GoldairFan
+from .geco_heater.climate import GoldairGECOHeater
 from .gpcv_heater.climate import GoldairGPCVHeater
 from .heater.climate import GoldairHeater
 from .kogan_heater.climate import KoganHeater
@@ -28,6 +30,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_CLIMATE] = GoldairDehumidifier(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_FAN:
         data[CONF_CLIMATE] = GoldairFan(device)
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_GECO_HEATER:
+        data[CONF_CLIMATE] = GoldairGECOHeater(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         data[CONF_CLIMATE] = GoldairGPCVHeater(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_KOGAN_HEATER:

+ 8 - 3
custom_components/tuya_local/configuration.py

@@ -1,8 +1,9 @@
 import voluptuous as vol
 from homeassistant.const import CONF_NAME, CONF_HOST
 
-from .const import (CONF_DEVICE_ID, CONF_LOCAL_KEY, CONF_TYPE, CONF_TYPE_HEATER,
-                    CONF_TYPE_DEHUMIDIFIER, CONF_TYPE_FAN,
+from .const import (CONF_DEVICE_ID, CONF_LOCAL_KEY, CONF_TYPE,
+                    CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER,
+                    CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER,
                     CONF_TYPE_GPCV_HEATER, CONF_TYPE_KOGAN_HEATER,
                     CONF_CLIMATE, CONF_DISPLAY_LIGHT, CONF_CHILD_LOCK,
                     CONF_TYPE_AUTO)
@@ -14,7 +15,11 @@ INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
     {"key": CONF_LOCAL_KEY, "type": str, "required": True, "option": True},
     {
         "key": CONF_TYPE,
-        "type": vol.In([CONF_TYPE_AUTO, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER, CONF_TYPE_FAN, CONF_TYPE_GPCV_HEATER, CONF_TYPE_KOGAN_HEATER]),
+        "type": vol.In([
+            CONF_TYPE_AUTO, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER,
+            CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER, CONF_TYPE_GPCV_HEATER,
+            CONF_TYPE_KOGAN_HEATER
+        ]),
         "required": False,
         "default": CONF_TYPE_AUTO,
         "option": True,

+ 1 - 0
custom_components/tuya_local/const.py

@@ -9,6 +9,7 @@ CONF_TYPE_AUTO = "auto"
 CONF_TYPE_HEATER = "heater"
 CONF_TYPE_DEHUMIDIFIER = "dehumidifier"
 CONF_TYPE_FAN = "fan"
+CONF_TYPE_GECO_HEATER = "geco_heater"
 CONF_TYPE_GPCV_HEATER = "gpcv_heater"
 CONF_TYPE_KOGAN_HEATER = "kogan_heater"
 CONF_CLIMATE = "climate"

+ 7 - 4
custom_components/tuya_local/device.py

@@ -10,9 +10,9 @@ from time import time
 from homeassistant.const import TEMP_CELSIUS
 
 from .const import (
-    DOMAIN, API_PROTOCOL_VERSIONS, CONF_TYPE_DEHUMIDIFIER,CONF_TYPE_FAN,
-    CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER, CONF_TYPE_KOGAN_HEATER)
-
+    DOMAIN, API_PROTOCOL_VERSIONS, CONF_TYPE_DEHUMIDIFIER, CONF_TYPE_FAN,
+    CONF_TYPE_GECO_HEATER, CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
+    CONF_TYPE_KOGAN_HEATER)
 _LOGGER = logging.getLogger(__name__)
 
 
@@ -84,7 +84,10 @@ class TuyaLocalDevice(object):
         _LOGGER.debug(f"Inferring device type from cached state: {cached_state}")
         if "5" in cached_state:
             if "3" in cached_state:
-                return CONF_TYPE_GPCV_HEATER
+                if "7" in cached_state:
+                    return CONF_TYPE_GPCV_HEATER
+                else:
+                    return CONF_TYPE_GECO_HEATER
             else:
                 return CONF_TYPE_DEHUMIDIFIER
         if "8" in cached_state:

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


+ 156 - 0
custom_components/tuya_local/geco_heater/climate.py

@@ -0,0 +1,156 @@
+"""
+Goldair GECO WiFi Heater device.
+"""
+from homeassistant.components.climate import ClimateDevice
+from homeassistant.components.climate.const import (
+    ATTR_HVAC_MODE,
+    HVAC_MODE_HEAT,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE
+
+from ..device import TuyaLocalDevice
+from .const import (
+    ATTR_ERROR,
+    ATTR_TARGET_TEMPERATURE,
+    HVAC_MODE_TO_DPS_MODE,
+    PROPERTY_TO_DPS_ID,
+)
+
+SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
+
+
+class GoldairGECOHeater(ClimateDevice):
+    """Representation of a Goldair GECO 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"
+        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])
+
+        if dps_mode is not None:
+            return TuyaLocalDevice.get_key_for_value(HVAC_MODE_TO_DPS_MODE, dps_mode)
+        else:
+            return STATE_UNAVAILABLE
+
+    @property
+    def hvac_modes(self):
+        """Return the list of available HVAC modes."""
+        return list(HVAC_MODE_TO_DPS_MODE.keys())
+
+    async def async_set_hvac_mode(self, hvac_mode):
+        """Set new HVAC mode."""
+        dps_mode = HVAC_MODE_TO_DPS_MODE[hvac_mode]
+        await self._device.async_set_property(
+            PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode
+        )
+
+    @property
+    def device_state_attributes(self):
+        """Get additional attributes that HA doesn't naturally support."""
+        error = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_ERROR])
+
+        return {ATTR_ERROR: error or None}
+
+    async def async_update(self):
+        await self._device.async_refresh()

+ 21 - 0
custom_components/tuya_local/geco_heater/const.py

@@ -0,0 +1,21 @@
+from homeassistant.components.climate.const import (
+    ATTR_HVAC_MODE,
+    ATTR_PRESET_MODE,
+    HVAC_MODE_HEAT,
+    HVAC_MODE_OFF,
+)
+from homeassistant.const import ATTR_TEMPERATURE
+
+ATTR_TARGET_TEMPERATURE = "target_temperature"
+ATTR_CHILD_LOCK = "child_lock"
+ATTR_ERROR = "error"
+
+PROPERTY_TO_DPS_ID = {
+    ATTR_HVAC_MODE: "1",
+    ATTR_CHILD_LOCK: "2",
+    ATTR_TARGET_TEMPERATURE: "3",
+    ATTR_TEMPERATURE: "4",
+    ATTR_ERROR: "6",
+}
+
+HVAC_MODE_TO_DPS_MODE = {HVAC_MODE_OFF: False, HVAC_MODE_HEAT: True}

+ 64 - 0
custom_components/tuya_local/geco_heater/lock.py

@@ -0,0 +1,64 @@
+"""
+Platform to control the child lock on Goldair GECO WiFi-connected heaters and panels.
+"""
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..device import TuyaLocalDevice
+from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
+
+
+class GoldairGECOHeaterChildLock(LockDevice):
+    """Representation of a Goldair GECO WiFi-connected heater child lock."""
+
+    def __init__(self, device):
+        """Initialize the lock.
+        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 lock."""
+        return self._device.name
+
+    @property
+    def unique_id(self):
+        """Return the unique id for this heater child lock."""
+        return self._device.unique_id
+
+    @property
+    def device_info(self):
+        """Return device information about this heater child lock."""
+        return self._device.device_info
+
+    @property
+    def state(self):
+        """Return the current state."""
+        if self.is_locked is None:
+            return STATE_UNAVAILABLE
+        else:
+            return STATE_LOCKED if self.is_locked else STATE_UNLOCKED
+
+    @property
+    def is_locked(self):
+        """Return the a boolean representing whether the child lock is on or not."""
+        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK])
+
+    async def async_lock(self, **kwargs):
+        """Turn on the child lock."""
+        await self._device.async_set_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], True)
+
+    async def async_unlock(self, **kwargs):
+        """Turn off the child lock."""
+        await self._device.async_set_property(
+            PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], False
+        )
+
+    async def async_update(self):
+        await self._device.async_refresh()

+ 6 - 2
custom_components/tuya_local/light.py

@@ -3,8 +3,10 @@ Setup for different kinds of Tuya climate devices
 """
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
-                    CONF_TYPE_FAN, CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
-                    CONF_TYPE_KOGAN_HEATER, CONF_DISPLAY_LIGHT, CONF_TYPE_AUTO)
+                    CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER,
+                    CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
+                    CONF_TYPE_KOGAN_HEATER, CONF_DISPLAY_LIGHT,
+                    CONF_TYPE_AUTO)
 from .dehumidifier.light import GoldairDehumidifierLedDisplayLight
 from .fan.light import GoldairFanLedDisplayLight
 from .heater.light import GoldairHeaterLedDisplayLight
@@ -29,6 +31,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_DISPLAY_LIGHT] = GoldairFanLedDisplayLight(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         raise ValueError("Goldair GPCV Heaters do not support panel lighting control.")
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_GECO_HEATER:
+        raise ValueError("Goldair GECO Heaters do not support panel lighting control.")
     elif discovery_info[CONF_TYPE] == CONF_TYPE_KOGAN_HEATER:
         raise ValueError('Kogan heaters do not support panel lighting control')
 

+ 5 - 1
custom_components/tuya_local/lock.py

@@ -3,10 +3,12 @@ Setup for different kinds of Tuya climate devices
 """
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
-                    CONF_TYPE_FAN, CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
+                    CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER,
+                    CONF_TYPE_GPCV_HEATER, CONF_TYPE_HEATER,
                     CONF_TYPE_KOGAN_HEATER, CONF_CHILD_LOCK, CONF_TYPE_AUTO)
 from .dehumidifier.lock import GoldairDehumidifierChildLock
 from .gpcv_heater.lock import GoldairGPCVHeaterChildLock
+from .geco_heater.lock import GoldairGECOHeaterChildLock
 from .heater.lock import GoldairHeaterChildLock
 from .kogan_heater.lock import KoganHeaterChildLock
 
@@ -25,6 +27,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_CHILD_LOCK] = GoldairHeaterChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
         data[CONF_CHILD_LOCK] = GoldairDehumidifierChildLock(device)
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_GECO_HEARER:
+        data[CONF_CHILD_LOCK] = GoldairGECOHeaterChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         data[CONF_CHILD_LOCK] = GoldairGPCVHeaterChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_KOGAN_HEATER: