Explorar el Código

Merge pull request #22 from nikrolls/feature/dehumidifier-support

Add support for Goldair dehumidifiers
Nik Rolls hace 6 años
padre
commit
6629746497

+ 22 - 28
README.md

@@ -2,10 +2,11 @@ Home Assistant Goldair WiFi Climate component
 =============================================
 
 The `goldair_climate` component integrates 
-[Goldair WiFi-enabled heaters](http://www.goldair.co.nz/product-catalogue/heating/wifi-heaters) into Home Assistant, 
+[Goldair WiFi-enabled heaters](http://www.goldair.co.nz/product-catalogue/heating/wifi-heaters) and
+WiFi-enabled [dehumidifiers](http://www.goldair.co.nz/product-catalogue/heating/dehumidifiers) into Home Assistant,
 enabling control of setting the following parameters via the UI and the following services:
 
-**Climate**
+**Heaters**
 * **power** (on/off)
 * **mode** (Comfort, Eco, Anti-freeze)
 * **target temperature** (`5`-`35` in Comfort mode, `5`-`21` in Eco mode, in °C)
@@ -13,8 +14,12 @@ enabling control of setting the following parameters via the UI and the followin
 
 Current temperature is also displayed.
 
-**Sensor**
-* **current temperature** (in °C)
+**Demudifiers**
+* **power** (on/off)
+* **mode** (Normal, Low, High, Dry clothes, Air clean)
+* **target humidity** (`30`-`80`%)
+
+Current temperature is displayed, and current humidity is available as a property.
 
 **Light**
 * **LED display** (on/off)
@@ -22,13 +27,15 @@ Current temperature is also displayed.
 **Lock**
 * **Child lock** (on/off)
 
+There was previously a sensor option, however this is easily achieved using a
+[template sensor](https://www.home-assistant.io/integrations/template/) and therefore is no longer supported.
+
 ---
 
 ### Warning
-Please note, this component has currently only been tested with the Goldair GPPH (inverter) range, however theoretically 
-it should also work with GEPH and GPCV devices and any other Goldair heaters based on the Tuya platform.
-
-Work is in progress to support Goldair WiFi dehumidifiers.
+Please note, this component has currently only been tested with the Goldair GPPH (inverter) and GPDH420 (dehumidifier)
+range, however theoretically it should also work with GEPH and GPCV heater devices, the GPDH440 dehumidifier, and any
+other Goldair heaters or dehumidifiers based on the Tuya platform.
 
 ---
 
@@ -38,10 +45,8 @@ The preferred installation method is via [HACS](https://hacs.xyz/). Once you hav
 [instructions for adding a custom repository](https://hacs.xyz/docs/navigation/settings#custom-repositories) and then
 the integration will be available to install like any other.
 
-You can also use [Custom Updater](https://github.com/custom-components/custom_updater). Once you have Custom Updater set
-up, simply go to the dev-service page
-<img src="https://www.home-assistant.io/images/screenshots/developer-tool-services-icon.png" alt="The dev-service icon" width="30">
-and call the `custom_updater.install` service with this service data:
+You can also use [Custom Updater](https://github.com/custom-components/custom_updater). Once Custom Updater is  set
+up, go to the Developer Tools > Service page and call the `custom_updater.install` service with this service data:
 ```json
 { "element": "goldair_climate" }
 ```
@@ -59,7 +64,7 @@ goldair_climate:
     host: 1.2.3.4
     device_id: <your device id>
     local_key: <your local key>
-    type: 'heater'
+    type: heater
 ```
 
 ### Configuration variables
@@ -80,21 +85,13 @@ goldair_climate:
                                               [as per the instructions below](#finding-your-device-id-and-local-key).
 
 #### type
-&nbsp;&nbsp;&nbsp;&nbsp;*(string) (Required)* The type of Goldair device. Currently `heater` is the only option; a 
-                                              future update will add support for dehumidifiers and other devices, so
-                                              setting the type now will prevent the integration breaking when this
-                                              functionality is released.
+&nbsp;&nbsp;&nbsp;&nbsp;*(string) (Required)* The type of Goldair device: currently `heater` or `dehumidifier`.
 
 #### climate
 &nbsp;&nbsp;&nbsp;&nbsp;*(boolean) (Optional)* Whether to surface this heater as a climate device.
 
 &nbsp;&nbsp;&nbsp;&nbsp;*Default value: true* 
 
-#### sensor
-&nbsp;&nbsp;&nbsp;&nbsp;*(boolean) (Optional)* Whether to surface this heater's thermometer as a temperature sensor.
-
-&nbsp;&nbsp;&nbsp;&nbsp;*Default value: false* 
-
 #### display_light
 &nbsp;&nbsp;&nbsp;&nbsp;*(boolean) (Optional)* Whether to surface this heater's LED display control as a light.
 
@@ -105,9 +102,9 @@ goldair_climate:
 
 &nbsp;&nbsp;&nbsp;&nbsp;*Default value: false* 
 
-Gotchas
--------
-These heaters have individual target temperatures for their Comfort and Eco modes, whereas Home Assistant only supports
+Heater gotchas
+--------------
+Goldair 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: 
@@ -139,9 +136,6 @@ Next steps
 This component needs specs! Once they're written I'm considering submitting it to the HA team for inclusion in standard 
 installations. Please report any issues and feel free to raise pull requests.
 
-I also have a working integration for Goldair WiFi dehumidifiers; it needs to be re-worked to prevent duplicate code
-before releasing it to the wild.
-
 Acknowledgements
 ----------------
 None of this would have been possible without some foundational discovery work to get me started:

+ 12 - 6
custom_components/goldair_climate/__init__.py

@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
 from homeassistant.const import (CONF_NAME, CONF_HOST, TEMP_CELSIUS)
 from homeassistant.helpers.discovery import load_platform
 
-VERSION = '0.0.5'
+VERSION = '0.0.6'
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -26,8 +26,8 @@ CONF_DEVICE_ID = 'device_id'
 CONF_LOCAL_KEY = 'local_key'
 CONF_TYPE = 'type'
 CONF_TYPE_HEATER = 'heater'
+CONF_TYPE_DEHUMIDIFIER = 'dehumidifier'
 CONF_CLIMATE = 'climate'
-CONF_SENSOR = 'sensor'
 CONF_DISPLAY_LIGHT = 'display_light'
 CONF_CHILD_LOCK = 'child_lock'
 
@@ -36,9 +36,8 @@ PLATFORM_SCHEMA = vol.Schema({
     vol.Required(CONF_HOST): cv.string,
     vol.Required(CONF_DEVICE_ID): cv.string,
     vol.Required(CONF_LOCAL_KEY): cv.string,
-    vol.Required(CONF_TYPE): vol.In([CONF_TYPE_HEATER]),
+    vol.Required(CONF_TYPE): vol.In([CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER]),
     vol.Optional(CONF_CLIMATE, default=True): cv.boolean,
-    vol.Optional(CONF_SENSOR, default=False): cv.boolean,
     vol.Optional(CONF_DISPLAY_LIGHT, default=False): cv.boolean,
     vol.Optional(CONF_CHILD_LOCK, default=False): cv.boolean,
 })
@@ -64,8 +63,6 @@ def setup(hass, config):
 
         if device_config.get(CONF_CLIMATE) == True:
             load_platform(hass, 'climate', DOMAIN, discovery_info, config)
-        if device_config.get(CONF_SENSOR) == True:
-            load_platform(hass, 'sensor', DOMAIN, discovery_info, config)
         if device_config.get(CONF_DISPLAY_LIGHT) == True:
             load_platform(hass, 'light', DOMAIN, discovery_info, config)
         if device_config.get(CONF_CHILD_LOCK) == True:
@@ -133,6 +130,15 @@ class GoldairTuyaDevice(object):
     def set_property(self, dps_id, value):
         self._set_properties({dps_id: value})
 
+    def anticipate_property_value(self, dps_id, value):
+        """
+        Update a value in the cached state only. This is good for when you know the device will reflect a new state in
+        the next update, but don't want to wait for that update for the device to represent this state.
+
+        The anticipated value will be cleared with the next update.
+        """
+        self._cached_state[dps_id] = value
+
     def _reset_cached_state(self):
         self._cached_state = {
             'updated_at': 0

+ 5 - 1
custom_components/goldair_climate/climate.py

@@ -3,12 +3,16 @@ Setup for different kinds of Goldair climate devices
 """
 from homeassistant.const import CONF_HOST
 from custom_components.goldair_climate import (
-    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER
+    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER
 )
 from custom_components.goldair_climate.heater.climate import GoldairHeater
+from custom_components.goldair_climate.dehumidifier.climate import GoldairDehumidifier
+
 
 def setup_platform(hass, config, add_devices, discovery_info=None):
     """Set up the Goldair climate device according to its type."""
     device = hass.data[DOMAIN][discovery_info[CONF_HOST]]
     if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
         add_devices([GoldairHeater(device)])
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
+        add_devices([GoldairDehumidifier(device)])

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


+ 250 - 0
custom_components/goldair_climate/dehumidifier/climate.py

@@ -0,0 +1,250 @@
+"""
+Goldair WiFi Heater device.
+"""
+from homeassistant.const import (
+    ATTR_TEMPERATURE, TEMP_CELSIUS, STATE_UNAVAILABLE
+)
+from homeassistant.components.climate import ClimateDevice
+from homeassistant.components.climate.const import (
+    ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODE, ATTR_PRESET_MODE,
+    FAN_OFF, FAN_LOW, FAN_HIGH,
+    HVAC_MODE_OFF, HVAC_MODE_DRY,
+    SUPPORT_TARGET_HUMIDITY, SUPPORT_PRESET_MODE, SUPPORT_FAN_MODE
+)
+from custom_components.goldair_climate import GoldairTuyaDevice
+
+ATTR_ON = 'on'
+ATTR_TARGET_HUMIDITY = 'target_humidity'
+ATTR_AIR_CLEAN_ON = 'air_clean_on'
+ATTR_CHILD_LOCK = 'child_lock'
+ATTR_FAULT = 'fault'
+ATTR_DISPLAY_ON = 'display_on'
+
+PRESET_NORMAL = 'Normal'
+PRESET_LOW = 'Low'
+PRESET_HIGH = 'High'
+PRESET_DRY_CLOTHES = 'Dry clothes'
+PRESET_AIR_CLEAN = 'Air clean'
+
+FAULT_NONE = 'No fault'
+FAULT_TANK = 'Tank full or missing'
+
+PROPERTY_TO_DPS_ID = {
+    ATTR_HVAC_MODE: '1',
+    ATTR_PRESET_MODE: '2',
+    ATTR_TARGET_HUMIDITY: '4',
+    ATTR_AIR_CLEAN_ON: '5',
+    ATTR_FAN_MODE: '6',
+    ATTR_CHILD_LOCK: '7',
+    ATTR_FAULT: '11',
+    ATTR_DISPLAY_ON: '102',
+    ATTR_TEMPERATURE: '103',
+    ATTR_HUMIDITY: '104'
+}
+
+HVAC_MODE_TO_DPS_MODE = {
+    HVAC_MODE_OFF: False,
+    HVAC_MODE_DRY: True
+}
+PRESET_MODE_TO_DPS_MODE = {
+    PRESET_NORMAL: '0',
+    PRESET_LOW: '1',
+    PRESET_HIGH: '2',
+    PRESET_DRY_CLOTHES: '3'
+}
+FAN_MODE_TO_DPS_MODE = {
+    FAN_LOW: '1',
+    FAN_HIGH: '3'
+}
+FAULT_CODE_TO_DPS_CODE = {
+    FAULT_NONE: 0,
+    FAULT_TANK: 8
+}
+
+SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY | SUPPORT_PRESET_MODE | SUPPORT_FAN_MODE
+
+
+class GoldairDehumidifier(ClimateDevice):
+    """Representation of a Goldair WiFi dehumidifier."""
+
+    def __init__(self, device):
+        """Initialize the dehumidifier.
+        Args:
+            name (str): The device's name.
+            device (GoldairTuyaDevice): The device API instance."""
+        self._device = device
+
+        self._support_flags = SUPPORT_FLAGS
+
+        self._HUMIDITY_STEP = 5
+        self._HUMIDITY_LIMITS = {
+            'min': 30,
+            'max': 80
+        }
+
+    @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 current_humidity(self):
+        """Return the current reading of the humidity sensor."""
+        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HUMIDITY])
+
+    @property
+    def min_humidity(self):
+        """Return the minimum humidity setting."""
+        return self._HUMIDITY_LIMITS['min']
+
+    @property
+    def max_humidity(self):
+        """Return the maximum humidity setting."""
+        return self._HUMIDITY_LIMITS['max']
+
+    @property
+    def target_humidity(self):
+        """Return the current target humidity."""
+        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_HUMIDITY])
+
+    def set_humidity(self, humidity):
+        """Set the device's target humidity."""
+        if self.preset_mode in [PRESET_AIR_CLEAN, PRESET_DRY_CLOTHES]:
+            raise ValueError('Humidity can only be changed while in Normal, Low or High preset modes.')
+        humidity = int(self._HUMIDITY_STEP * round(float(humidity) / self._HUMIDITY_STEP))
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_HUMIDITY], humidity)
+
+    @property
+    def temperature_unit(self):
+        """Return the unit of measurement."""
+        return self._device.temperature_unit
+
+    @property
+    def min_temp(self):
+        """Return the minimum temperature setting."""
+        return None
+
+    @property
+    def max_temp(self):
+        """Return the maximum temperature setting."""
+        return None
+
+    @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 Dry 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())
+
+    def set_hvac_mode(self, hvac_mode):
+        """Set new HVAC mode."""
+        dps_mode = HVAC_MODE_TO_DPS_MODE[hvac_mode]
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode)
+
+    @property
+    def preset_mode(self):
+        """Return current preset mode, ie Normal, Low, High, Dry Clothes, or Air Clean."""
+        air_clean_on = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_AIR_CLEAN_ON])
+        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE])
+
+        if air_clean_on:
+            return PRESET_AIR_CLEAN
+        elif dps_mode is not None:
+            return GoldairTuyaDevice.get_key_for_value(PRESET_MODE_TO_DPS_MODE, dps_mode)
+        else:
+            return None
+
+    @property
+    def preset_modes(self):
+        """Return the list of available preset modes."""
+        return list(PRESET_MODE_TO_DPS_MODE.keys()) + [PRESET_AIR_CLEAN]
+
+    def set_preset_mode(self, preset_mode):
+        """Set new preset mode."""
+        if preset_mode == PRESET_AIR_CLEAN:
+            self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_AIR_CLEAN_ON], True)
+            self._device.anticipate_property_value(PROPERTY_TO_DPS_ID[ATTR_FAN_MODE], FAN_HIGH)
+        else:
+            dps_mode = PRESET_MODE_TO_DPS_MODE[preset_mode]
+            self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_AIR_CLEAN_ON], False)
+            self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE], dps_mode)
+            if preset_mode == PRESET_LOW:
+                self._device.anticipate_property_value(PROPERTY_TO_DPS_ID[ATTR_FAN_MODE], FAN_LOW)
+            elif preset_mode in [PRESET_HIGH, PRESET_DRY_CLOTHES]:
+                self._device.anticipate_property_value(PROPERTY_TO_DPS_ID[ATTR_FAN_MODE], FAN_HIGH)
+
+    @property
+    def fan_mode(self):
+        """Return the fan mode."""
+        preset = self.preset_mode
+
+        if preset in [PRESET_HIGH, PRESET_DRY_CLOTHES, PRESET_AIR_CLEAN]:
+            return FAN_HIGH
+        elif preset == PRESET_LOW:
+            return FAN_LOW
+        else:
+            dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_FAN_MODE])
+            if dps_mode is not None:
+                return GoldairTuyaDevice.get_key_for_value(FAN_MODE_TO_DPS_MODE, dps_mode)
+            else:
+                return None
+
+    @property
+    def fan_modes(self):
+        """List of fan modes."""
+        preset = self.preset_mode
+
+        if preset in [PRESET_HIGH, PRESET_DRY_CLOTHES, PRESET_AIR_CLEAN]:
+            return [FAN_HIGH]
+        elif preset == PRESET_LOW:
+            return [FAN_LOW]
+        elif preset == PRESET_NORMAL:
+            return list(FAN_MODE_TO_DPS_MODE.keys())
+        else:
+            return []
+
+    def set_fan_mode(self, fan_mode):
+        """Set new fan mode."""
+        if self.preset_mode != PRESET_NORMAL:
+            raise ValueError('Fan mode can only be changed while in Normal preset mode.')
+
+        if fan_mode not in FAN_MODE_TO_DPS_MODE.keys():
+            raise ValueError(f'Invalid fan mode: {fan_mode}')
+
+        dps_mode = FAN_MODE_TO_DPS_MODE[fan_mode]
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_FAN_MODE], dps_mode)
+
+    @property
+    def fault(self):
+        """Get the current fault status."""
+        fault = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_FAULT])
+        if fault is None or fault == FAULT_NONE:
+            return None
+        else:
+            return GoldairTuyaDevice.get_key_for_value(FAULT_CODE_TO_DPS_CODE, fault)
+
+    def update(self):
+        self._device.refresh()

+ 56 - 0
custom_components/goldair_climate/dehumidifier/light.py

@@ -0,0 +1,56 @@
+"""
+Platform to control the LED display light on Goldair WiFi-connected dehumidifiers.
+"""
+from homeassistant.components.light import Light
+from homeassistant.const import STATE_UNAVAILABLE
+from custom_components.goldair_climate import GoldairTuyaDevice
+from custom_components.goldair_climate.dehumidifier.climate import (
+    ATTR_DISPLAY_ON, PROPERTY_TO_DPS_ID, HVAC_MODE_TO_DPS_MODE
+)
+from homeassistant.components.climate import (
+    ATTR_HVAC_MODE, HVAC_MODE_OFF
+)
+
+
+class GoldairDehumidifierLedDisplayLight(Light):
+    """Representation of a Goldair WiFi-connected dehumidifier LED display."""
+
+    def __init__(self, device):
+        """Initialize the light.
+        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 light."""
+        return self._device.name
+
+    @property
+    def is_on(self):
+        """Return the current state."""
+        dps_hvac_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
+        dps_display_on = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
+
+        if dps_hvac_mode is None or dps_hvac_mode == HVAC_MODE_TO_DPS_MODE[HVAC_MODE_OFF]:
+            return STATE_UNAVAILABLE
+        else:
+            return dps_display_on
+
+    def turn_on(self):
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], True)
+
+    def turn_off(self):
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], False)
+
+    def toggle(self):
+        dps_hvac_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
+        dps_display_on = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
+
+        if dps_hvac_mode != HVAC_MODE_TO_DPS_MODE[HVAC_MODE_OFF]:
+            self.turn_on() if not dps_display_on else self.turn_off()

+ 50 - 0
custom_components/goldair_climate/dehumidifier/lock.py

@@ -0,0 +1,50 @@
+"""
+Platform to control the child lock on Goldair WiFi-connected dehumidifiers.
+"""
+from homeassistant.components.lock import (STATE_LOCKED, STATE_UNLOCKED, LockDevice)
+from homeassistant.const import STATE_UNAVAILABLE
+from custom_components.goldair_climate import GoldairTuyaDevice
+from custom_components.goldair_climate.dehumidifier.climate import (
+    ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
+)
+
+
+class GoldairDehumidifierChildLock(LockDevice):
+    """Representation of a Goldair WiFi-connected dehumidifier 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 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])
+
+    def lock(self, **kwargs):
+        """Turn on the child lock."""
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], True)
+
+    def unlock(self, **kwargs):
+        """Turn off the child lock."""
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], False)

+ 30 - 39
custom_components/goldair_climate/heater/climate.py

@@ -4,10 +4,10 @@ Goldair WiFi Heater device.
 from homeassistant.const import (
     ATTR_TEMPERATURE, TEMP_CELSIUS, STATE_UNAVAILABLE
 )
-from homeassistant.components.climate import (
-  ClimateDevice, ATTR_HVAC_MODE, ATTR_PRESET_MODE, ATTR_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT
-)
+from homeassistant.components.climate import ClimateDevice
 from homeassistant.components.climate.const import (
+    ATTR_HVAC_MODE, ATTR_PRESET_MODE,
+    HVAC_MODE_OFF, HVAC_MODE_HEAT,
     SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE
 )
 from custom_components.goldair_climate import GoldairTuyaDevice
@@ -19,8 +19,6 @@ ATTR_FAULT = 'fault'
 ATTR_POWER_MODE_AUTO = 'auto'
 ATTR_POWER_MODE_USER = 'user'
 ATTR_POWER_LEVEL = 'power_level'
-ATTR_TIMER_MINUTES = 'timer_minutes'
-ATTR_TIMER_ON = 'timer_on'
 ATTR_DISPLAY_ON = 'display_on'
 ATTR_POWER_MODE = 'power_mode'
 ATTR_ECO_TARGET_TEMPERATURE = 'eco_' + ATTR_TARGET_TEMPERATURE
@@ -29,7 +27,7 @@ STATE_COMFORT = 'Comfort'
 STATE_ECO = 'Eco'
 STATE_ANTI_FREEZE = 'Anti-freeze'
 
-GOLDAIR_PROPERTY_TO_DPS_ID = {
+PROPERTY_TO_DPS_ID = {
     ATTR_HVAC_MODE: '1',
     ATTR_TARGET_TEMPERATURE: '2',
     ATTR_TEMPERATURE: '3',
@@ -37,23 +35,21 @@ GOLDAIR_PROPERTY_TO_DPS_ID = {
     ATTR_CHILD_LOCK: '6',
     ATTR_FAULT: '12',
     ATTR_POWER_LEVEL: '101',
-    ATTR_TIMER_MINUTES: '102',
-    ATTR_TIMER_ON: '103',
     ATTR_DISPLAY_ON: '104',
     ATTR_POWER_MODE: '105',
     ATTR_ECO_TARGET_TEMPERATURE: '106'
 }
 
-GOLDAIR_MODE_TO_HVAC_MODE = {
+HVAC_MODE_TO_DPS_MODE = {
     HVAC_MODE_OFF: False,
     HVAC_MODE_HEAT: True
 }
-GOLDAIR_MODE_TO_PRESET_MODE = {
+PRESET_MODE_TO_DPS_MODE = {
     STATE_COMFORT: 'C',
     STATE_ECO: 'ECO',
     STATE_ANTI_FREEZE: 'AF'
 }
-GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL = {
+POWER_LEVEL_TO_DPS_LEVEL = {
     'Stop': 'stop',
     '1': '1',
     '2': '2',
@@ -62,10 +58,10 @@ GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL = {
     '5': '5',
     'Auto': 'auto'
 }
-GOLDAIR_POWER_MODES = [ATTR_POWER_MODE_USER, ATTR_POWER_MODE_USER]
 
 SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE
 
+
 class GoldairHeater(ClimateDevice):
     """Representation of a Goldair WiFi heater."""
 
@@ -105,11 +101,6 @@ class GoldairHeater(ClimateDevice):
         """Return the name of the climate device."""
         return self._device.name
 
-    @property
-    def state(self):
-        """Return the state of the climate device."""
-        return self.hvac_mode
-
     @property
     def temperature_unit(self):
         """Return the unit of measurement."""
@@ -119,9 +110,9 @@ class GoldairHeater(ClimateDevice):
     def target_temperature(self):
         """Return the temperature we try to reach."""
         if self.preset_mode == STATE_COMFORT:
-            return self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE])
+            return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE])
         elif self.preset_mode == STATE_ECO:
-            return self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE])
+            return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE])
         else:
             return None
 
@@ -168,77 +159,77 @@ class GoldairHeater(ClimateDevice):
             )
 
         if preset_mode == STATE_COMFORT:
-            self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE], target_temperature)
+            self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE], target_temperature)
         elif preset_mode == STATE_ECO:
-            self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE], target_temperature)
+            self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE], target_temperature)
 
     @property
     def current_temperature(self):
         """Return the current temperature."""
-        return self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_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(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
+        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(GOLDAIR_MODE_TO_HVAC_MODE, dps_mode)
+            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(GOLDAIR_MODE_TO_HVAC_MODE.keys())
+        return list(HVAC_MODE_TO_DPS_MODE.keys())
 
     def set_hvac_mode(self, hvac_mode):
         """Set new HVAC mode."""
-        dps_mode = GOLDAIR_MODE_TO_HVAC_MODE[hvac_mode]
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode)
+        dps_mode = HVAC_MODE_TO_DPS_MODE[hvac_mode]
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode)
 
     @property
     def preset_mode(self):
         """Return current preset mode, ie Comfort, Eco, Anti-freeze."""
-        dps_mode = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE])
+        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE])
         if dps_mode is not None:
-            return GoldairTuyaDevice.get_key_for_value(GOLDAIR_MODE_TO_PRESET_MODE, dps_mode)
+            return GoldairTuyaDevice.get_key_for_value(PRESET_MODE_TO_DPS_MODE, dps_mode)
         else:
             return None
 
     @property
     def preset_modes(self):
         """Return the list of available preset modes."""
-        return list(GOLDAIR_MODE_TO_PRESET_MODE.keys())
+        return list(PRESET_MODE_TO_DPS_MODE.keys())
 
     def set_preset_mode(self, preset_mode):
         """Set new preset mode."""
-        dps_mode = GOLDAIR_MODE_TO_PRESET_MODE[preset_mode]
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE], dps_mode)
+        dps_mode = PRESET_MODE_TO_DPS_MODE[preset_mode]
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE], dps_mode)
 
     @property
     def swing_mode(self):
         """Return the power level."""
-        dps_mode = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_POWER_MODE])
+        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_MODE])
         if dps_mode == ATTR_POWER_MODE_USER:
-            return self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
+            return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
         elif dps_mode == ATTR_POWER_MODE_AUTO:
-            return GoldairTuyaDevice.get_key_for_value(GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL, dps_mode)
+            return GoldairTuyaDevice.get_key_for_value(POWER_LEVEL_TO_DPS_LEVEL, dps_mode)
         else:
             return None
 
     @property
     def swing_modes(self):
         """List of power levels."""
-        return list(GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL.keys())
+        return list(POWER_LEVEL_TO_DPS_LEVEL.keys())
 
     def set_swing_mode(self, swing_mode):
         """Set new power level."""
         new_level = swing_mode
-        if new_level not in GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL.keys():
+        if new_level not in POWER_LEVEL_TO_DPS_LEVEL.keys():
             raise ValueError(f'Invalid power level: {new_level}')
-        dps_level = GOLDAIR_POWER_LEVEL_TO_DPS_LEVEL[new_level]
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL], dps_level)
+        dps_level = POWER_LEVEL_TO_DPS_LEVEL[new_level]
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL], dps_level)
 
     def update(self):
         self._device.refresh()

+ 10 - 12
custom_components/goldair_climate/heater/light.py

@@ -5,14 +5,12 @@ from homeassistant.components.light import Light
 from homeassistant.const import STATE_UNAVAILABLE
 from custom_components.goldair_climate import GoldairTuyaDevice
 from custom_components.goldair_climate.heater.climate import (
-  ATTR_DISPLAY_ON, GOLDAIR_PROPERTY_TO_DPS_ID, GOLDAIR_MODE_TO_HVAC_MODE
+    ATTR_DISPLAY_ON, PROPERTY_TO_DPS_ID, HVAC_MODE_TO_DPS_MODE
 )
 from homeassistant.components.climate import (
-  ATTR_HVAC_MODE, HVAC_MODE_OFF
+    ATTR_HVAC_MODE, HVAC_MODE_OFF
 )
 
-import logging
-_LOGGER = logging.getLogger(__name__)
 
 class GoldairHeaterLedDisplayLight(Light):
     """Representation of a Goldair WiFi-connected heater LED display."""
@@ -36,23 +34,23 @@ class GoldairHeaterLedDisplayLight(Light):
     @property
     def is_on(self):
         """Return the current state."""
-        dps_hvac_mode = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
-        dps_display_on = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
+        dps_hvac_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
+        dps_display_on = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
 
-        if dps_hvac_mode is None or dps_hvac_mode == GOLDAIR_MODE_TO_HVAC_MODE[HVAC_MODE_OFF]:
+        if dps_hvac_mode is None or dps_hvac_mode == HVAC_MODE_TO_DPS_MODE[HVAC_MODE_OFF]:
             return STATE_UNAVAILABLE
         else:
             return dps_display_on
 
     def turn_on(self):
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], True)
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], True)
 
     def turn_off(self):
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], False)
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON], False)
 
     def toggle(self):
-        dps_hvac_mode = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
-        dps_display_on = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
+        dps_hvac_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
+        dps_display_on = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_DISPLAY_ON])
 
-        if dps_hvac_mode != GOLDAIR_MODE_TO_HVAC_MODE[HVAC_MODE_OFF]:
+        if dps_hvac_mode != HVAC_MODE_TO_DPS_MODE[HVAC_MODE_OFF]:
             self.turn_on() if not dps_display_on else self.turn_off()

+ 5 - 4
custom_components/goldair_climate/heater/lock.py

@@ -5,9 +5,10 @@ from homeassistant.components.lock import (STATE_LOCKED, STATE_UNLOCKED, LockDev
 from homeassistant.const import STATE_UNAVAILABLE
 from custom_components.goldair_climate import GoldairTuyaDevice
 from custom_components.goldair_climate.heater.climate import (
-  ATTR_CHILD_LOCK, GOLDAIR_PROPERTY_TO_DPS_ID, GOLDAIR_MODE_TO_HVAC_MODE
+    ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 )
 
+
 class GoldairHeaterChildLock(LockDevice):
     """Representation of a Goldair WiFi-connected heater child lock."""
 
@@ -38,12 +39,12 @@ class GoldairHeaterChildLock(LockDevice):
     @property
     def is_locked(self):
         """Return the a boolean representing whether the child lock is on or not."""
-        return self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK])
+        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK])
 
     def lock(self, **kwargs):
         """Turn on the child lock."""
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], True)
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], True)
 
     def unlock(self, **kwargs):
         """Turn off the child lock."""
-        self._device.set_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], False)
+        self._device.set_property(PROPERTY_TO_DPS_ID[ATTR_CHILD_LOCK], False)

+ 0 - 41
custom_components/goldair_climate/heater/sensor.py

@@ -1,41 +0,0 @@
-"""
-Platform to sense the current temperature at a Goldair WiFi-connected heaters and panels.
-"""
-from homeassistant.helpers.entity import Entity
-from homeassistant.const import (
-    STATE_UNAVAILABLE, ATTR_TEMPERATURE
-)
-from custom_components.goldair_climate import GoldairTuyaDevice
-from custom_components.goldair_climate.heater.climate import GOLDAIR_PROPERTY_TO_DPS_ID
-
-class GoldairHeaterTemperatureSensor(Entity):
-    """Representation of a Goldair WiFi-connected heater thermometer."""
-
-    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 sensor."""
-        return self._device.name
-
-    @property
-    def state(self):
-        """Return the current temperature."""
-        current_temperature = self._device.get_property(GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_TEMPERATURE])
-        if current_temperature is None:
-            return STATE_UNAVAILABLE
-        else:
-            return current_temperature
-
-    @property
-    def unit_of_measurement(self):
-        return self._device.temperature_unit

+ 5 - 1
custom_components/goldair_climate/light.py

@@ -3,12 +3,16 @@ Setup for different kinds of Goldair climate devices
 """
 from homeassistant.const import CONF_HOST
 from custom_components.goldair_climate import (
-    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER
+    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER
 )
 from custom_components.goldair_climate.heater.light import GoldairHeaterLedDisplayLight
+from custom_components.goldair_climate.dehumidifier.light import GoldairDehumidifierLedDisplayLight
+
 
 def setup_platform(hass, config, add_devices, discovery_info=None):
     """Set up the Goldair climate device according to its type."""
     device = hass.data[DOMAIN][discovery_info[CONF_HOST]]
     if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
         add_devices([GoldairHeaterLedDisplayLight(device)])
+    elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
+        add_devices([GoldairDehumidifierLedDisplayLight(device)])

+ 5 - 1
custom_components/goldair_climate/lock.py

@@ -3,12 +3,16 @@ Setup for different kinds of Goldair climate devices
 """
 from homeassistant.const import CONF_HOST
 from custom_components.goldair_climate import (
-    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER
+    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER, CONF_TYPE_DEHUMIDIFIER
 )
 from custom_components.goldair_climate.heater.lock import GoldairHeaterChildLock
+from custom_components.goldair_climate.dehumidifier.lock import GoldairDehumidifierChildLock
+
 
 def setup_platform(hass, config, add_devices, discovery_info=None):
     """Set up the Goldair climate device according to its type."""
     device = hass.data[DOMAIN][discovery_info[CONF_HOST]]
     if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
         add_devices([GoldairHeaterChildLock(device)])
+    if discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
+        add_devices([GoldairDehumidifierChildLock(device)])

+ 6 - 2
custom_components/goldair_climate/manifest.json

@@ -3,7 +3,11 @@
   "name": "Goldair WiFi climate devices",
   "documentation": "https://github.com/nikrolls/homeassistant-goldair-climate",
   "dependencies": [],
-  "codeowners": ["@nikrolls"],
-  "requirements": ["pytuya>=7.0.5"],
+  "codeowners": [
+    "@nikrolls"
+  ],
+  "requirements": [
+    "pytuya>=7.0.5"
+  ],
   "homeassistant": "0.96.0"
 }

+ 0 - 14
custom_components/goldair_climate/sensor.py

@@ -1,14 +0,0 @@
-"""
-Setup for different kinds of Goldair climate devices
-"""
-from homeassistant.const import CONF_HOST
-from custom_components.goldair_climate import (
-    DOMAIN, CONF_TYPE, CONF_TYPE_HEATER
-)
-from custom_components.goldair_climate.heater.sensor import GoldairHeaterTemperatureSensor
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
-    """Set up the Goldair climate device according to its type."""
-    device = hass.data[DOMAIN][discovery_info[CONF_HOST]]
-    if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
-        add_devices([GoldairHeaterTemperatureSensor(device)])

+ 5 - 3
custom_updater.json

@@ -1,6 +1,6 @@
 {
     "goldair_climate": {
-        "version": "0.0.5",
+        "version": "0.0.6",
         "local_location": "/custom_components/goldair_climate/__init__.py",
         "remote_location": "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/__init__.py",
         "visit_repo": "https://github.com/nikrolls/homeassistant-goldair-climate",
@@ -11,12 +11,14 @@
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/climate.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/light.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/lock.py",
-            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/sensor.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/heater/__init__.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/heater/climate.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/heater/light.py",
             "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/heater/lock.py",
-            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/heater/sensor.py"
+            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/dehumidifier/__init__.py",
+            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/dehumidifier/climate.py",
+            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/dehumidifier/light.py",
+            "https://raw.githubusercontent.com/nikrolls/homeassistant-goldair-climate/master/custom_components/goldair_climate/dehumidifier/lock.py"
         ]
     }
 }

+ 14 - 0
hacs.json

@@ -0,0 +1,14 @@
+{
+  "name": "Goldair WiFi climate devices",
+  "render_readme": true,
+  "domains": [
+    "climate",
+    "light",
+    "lock"
+  ],
+  "country": [
+    "NZ",
+    "AU"
+  ],
+  "homeassistant": "0.96.0"
+}