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

Add support for GECO Heaters

Speculative based on changes in KiLLeRRat/homeassistant-goldair-climate and the manual for this model.
The DPS arrangement seems the same as GPCV, but this model does not have the High and Low modes which we exposed as PRESET_MODE in the GPCV so may generate errors if configured as GPCV.

This heater appears cosmetically and functionally the same as GEPH, so hopefully it works with these as well.
Jason Rumney 5 лет назад
Родитель
Сommit
19782a252d

+ 5 - 1
custom_components/goldair_climate/climate.py

@@ -3,10 +3,12 @@ Setup for different kinds of Goldair climate devices
 """
 """
 from . import DOMAIN
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
 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_CLIMATE, CONF_TYPE_AUTO)
                     CONF_CLIMATE, CONF_TYPE_AUTO)
 from .dehumidifier.climate import GoldairDehumidifier
 from .dehumidifier.climate import GoldairDehumidifier
 from .fan.climate import GoldairFan
 from .fan.climate import GoldairFan
+from .geco_heater.climate import GoldairGECOHeater
 from .gpcv_heater.climate import GoldairGPCVHeater
 from .gpcv_heater.climate import GoldairGPCVHeater
 from .heater.climate import GoldairHeater
 from .heater.climate import GoldairHeater
 
 
@@ -28,6 +30,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_CLIMATE] = GoldairDehumidifier(device)
         data[CONF_CLIMATE] = GoldairDehumidifier(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_FAN:
     elif discovery_info[CONF_TYPE] == CONF_TYPE_FAN:
         data[CONF_CLIMATE] = GoldairFan(device)
         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:
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         data[CONF_CLIMATE] = GoldairGPCVHeater(device)
         data[CONF_CLIMATE] = GoldairGPCVHeater(device)
 
 

+ 7 - 5
custom_components/goldair_climate/configuration.py

@@ -1,10 +1,12 @@
 import voluptuous as vol
 import voluptuous as vol
 from homeassistant.const import CONF_NAME, CONF_HOST
 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,
-                    CONF_TYPE_GPCV_HEATER, CONF_CLIMATE, CONF_DISPLAY_LIGHT,
-                    CONF_CHILD_LOCK, CONF_TYPE_AUTO)
+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_CLIMATE,
+                    CONF_DISPLAY_LIGHT, CONF_CHILD_LOCK,
+                    CONF_TYPE_AUTO)
 
 
 INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
 INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
     {"key": CONF_NAME, "type": str, "required": True, "option": False},
     {"key": CONF_NAME, "type": str, "required": True, "option": False},
@@ -13,7 +15,7 @@ INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
     {"key": CONF_LOCAL_KEY, "type": str, "required": True, "option": True},
     {"key": CONF_LOCAL_KEY, "type": str, "required": True, "option": True},
     {
     {
         "key": CONF_TYPE,
         "key": CONF_TYPE,
-        "type": vol.In([CONF_TYPE_AUTO, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER, CONF_TYPE_FAN, CONF_TYPE_GPCV_HEATER]),
+        "type": vol.In([CONF_TYPE_AUTO, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER, CONF_TYPE_FAN, CONF_TYPE_GECO_HEATER, CONF_TYPE_GPCV_HEATER]),
         "required": False,
         "required": False,
         "default": CONF_TYPE_AUTO,
         "default": CONF_TYPE_AUTO,
         "option": True,
         "option": True,

+ 1 - 0
custom_components/goldair_climate/const.py

@@ -10,6 +10,7 @@ CONF_TYPE_HEATER = "heater"
 CONF_TYPE_DEHUMIDIFIER = "dehumidifier"
 CONF_TYPE_DEHUMIDIFIER = "dehumidifier"
 CONF_TYPE_FAN = "fan"
 CONF_TYPE_FAN = "fan"
 CONF_TYPE_GPCV_HEATER = "gpcv_heater"
 CONF_TYPE_GPCV_HEATER = "gpcv_heater"
+CONF_TYPE_GECO_HEATER = "geco_heater"
 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"

+ 7 - 2
custom_components/goldair_climate/device.py

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

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


+ 156 - 0
custom_components/goldair_climate/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 GoldairTuyaDevice
+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 (GoldairTuyaDevice): 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 GoldairTuyaDevice.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/goldair_climate/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/goldair_climate/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 GoldairTuyaDevice
+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 (GoldairTuyaDevice): 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()

+ 4 - 1
custom_components/goldair_climate/light.py

@@ -3,7 +3,8 @@ Setup for different kinds of Goldair climate devices
 """
 """
 from . import DOMAIN
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
 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_DISPLAY_LIGHT, CONF_TYPE_AUTO)
                     CONF_DISPLAY_LIGHT, CONF_TYPE_AUTO)
 from .dehumidifier.light import GoldairDehumidifierLedDisplayLight
 from .dehumidifier.light import GoldairDehumidifierLedDisplayLight
 from .fan.light import GoldairFanLedDisplayLight
 from .fan.light import GoldairFanLedDisplayLight
@@ -29,6 +30,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_DISPLAY_LIGHT] = GoldairFanLedDisplayLight(device)
         data[CONF_DISPLAY_LIGHT] = GoldairFanLedDisplayLight(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         raise ValueError("Goldair GPCV Heaters do not support panel lighting control.")
         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.")
 
 
     if CONF_DISPLAY_LIGHT in data:
     if CONF_DISPLAY_LIGHT in data:
         async_add_entities([data[CONF_DISPLAY_LIGHT]])
         async_add_entities([data[CONF_DISPLAY_LIGHT]])

+ 5 - 1
custom_components/goldair_climate/lock.py

@@ -3,10 +3,12 @@ Setup for different kinds of Goldair climate devices
 """
 """
 from . import DOMAIN
 from . import DOMAIN
 from .const import (CONF_DEVICE_ID, CONF_TYPE, CONF_TYPE_DEHUMIDIFIER,
 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_CHILD_LOCK, CONF_TYPE_AUTO)
                     CONF_CHILD_LOCK, CONF_TYPE_AUTO)
 from .dehumidifier.lock import GoldairDehumidifierChildLock
 from .dehumidifier.lock import GoldairDehumidifierChildLock
 from .gpcv_heater.lock import GoldairGPCVHeaterChildLock
 from .gpcv_heater.lock import GoldairGPCVHeaterChildLock
+from .geco_heater.lock import GoldairGECOHeaterChildLock
 from .heater.lock import GoldairHeaterChildLock
 from .heater.lock import GoldairHeaterChildLock
 
 
 
 
@@ -27,6 +29,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         data[CONF_CHILD_LOCK] = GoldairDehumidifierChildLock(device)
         data[CONF_CHILD_LOCK] = GoldairDehumidifierChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_FAN:
     elif discovery_info[CONF_TYPE] == CONF_TYPE_FAN:
         raise ValueError("Goldair fans do not support child lock.")
         raise ValueError("Goldair fans do not support child lock.")
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_GECO_HEARER:
+        data[CONF_CHILD_LOCK] = GoldairGECOHeaterChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
     elif discovery_info[CONF_TYPE] == CONF_TYPE_GPCV_HEATER:
         data[CONF_CHILD_LOCK] = GoldairGPCVHeaterChildLock(device)
         data[CONF_CHILD_LOCK] = GoldairGPCVHeaterChildLock(device)