Ver Fonte

Merge homeassistant-goldair-climate' 0.1.2rc

Fixes deprecation warnings from HA 0.110.
Jason Rumney há 5 anos atrás
pai
commit
e0f7ca17dc
35 ficheiros alterados com 299 adições e 57 exclusões
  1. 6 0
      .coveragerc
  2. 1 1
      .devcontainer/devcontainer.json
  3. 5 6
      .github/workflows/hacs-validate.yml
  4. 3 4
      .github/workflows/hassfest-validate.yml
  5. 4 2
      .github/workflows/linting.yml
  6. 33 0
      .github/workflows/tests.yml
  7. 3 1
      .gitignore
  8. 2 2
      custom_components/tuya_local/__init__.py
  9. 3 2
      custom_components/tuya_local/climate.py
  10. 2 2
      custom_components/tuya_local/configuration.py
  11. 1 1
      custom_components/tuya_local/const.py
  12. 6 2
      custom_components/tuya_local/dehumidifier/climate.py
  13. 6 2
      custom_components/tuya_local/dehumidifier/light.py
  14. 7 2
      custom_components/tuya_local/dehumidifier/lock.py
  15. 2 2
      custom_components/tuya_local/device.py
  16. 6 2
      custom_components/tuya_local/fan/climate.py
  17. 6 2
      custom_components/tuya_local/fan/light.py
  18. 6 2
      custom_components/tuya_local/geco_heater/climate.py
  19. 7 2
      custom_components/tuya_local/geco_heater/lock.py
  20. 6 2
      custom_components/tuya_local/gpcv_heater/climate.py
  21. 7 2
      custom_components/tuya_local/gpcv_heater/lock.py
  22. 6 2
      custom_components/tuya_local/heater/climate.py
  23. 6 2
      custom_components/tuya_local/heater/light.py
  24. 7 2
      custom_components/tuya_local/heater/lock.py
  25. 5 2
      custom_components/tuya_local/kogan_heater/climate.py
  26. 7 2
      custom_components/tuya_local/kogan_heater/lock.py
  27. 2 2
      custom_components/tuya_local/light.py
  28. 2 2
      custom_components/tuya_local/lock.py
  29. 7 0
      requirements-dev.txt
  30. 1 0
      requirements-first.txt
  31. 2 2
      requirements.txt
  32. 6 0
      sonar-project.properties
  33. 0 0
      tests/__init__.py
  34. 44 0
      tests/const.py
  35. 82 0
      tests/test_device.py

+ 6 - 0
.coveragerc

@@ -0,0 +1,6 @@
+[run]
+branch = True
+
+[report]
+skip_empty = True
+include = custom_components/*

+ 1 - 1
.devcontainer/devcontainer.json

@@ -15,7 +15,7 @@
     "files.exclude": {
       "config/custom_components/goldair_climate": true
     },
-    "python.pythonPath": "/usr/local/bin/python",
+    "python.pythonPath": "/usr/bin/python",
     "terminal.integrated.shell.linux": "/bin/bash"
   },
   "mounts": [

+ 5 - 6
.github/workflows/hacs-validate.yml

@@ -2,17 +2,16 @@ name: Validate with HACS
 
 on:
   push:
-  pull_request:
   schedule:
-    - cron: "0 0 * * *"
+    - cron: '0 0 * * *'
 
 jobs:
   validate:
-    runs-on: "ubuntu-latest"
+    runs-on: 'ubuntu-latest'
     steps:
-      - uses: "actions/checkout@v2"
+      - uses: 'actions/checkout@v2'
       - name: HACS validation
-        uses: "hacs/integration/action@master"
+        uses: 'hacs/integration/action@master'
         with:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          CATEGORY: "integration"
+          CATEGORY: 'integration'

+ 3 - 4
.github/workflows/hassfest-validate.yml

@@ -2,13 +2,12 @@ name: Validate with hassfest
 
 on:
   push:
-  pull_request:
   schedule:
-    - cron: "0 0 * * *"
+    - cron: '0 0 * * *'
 
 jobs:
   validate:
-    runs-on: "ubuntu-latest"
+    runs-on: 'ubuntu-latest'
     steps:
-      - uses: "actions/checkout@v2"
+      - uses: 'actions/checkout@v2'
       - uses: home-assistant/actions/hassfest@master

+ 4 - 2
.github/workflows/linting.yml

@@ -1,6 +1,6 @@
 name: Linting
 
-on: [push, pull_request]
+on: push
 
 jobs:
   lint:
@@ -14,7 +14,9 @@ jobs:
           python-version: 3.7
 
       - name: Install dependencies
-        run: pip install --pre -r requirements.txt
+        run: |
+          pip install -r requirements-first.txt
+          pip install --pre -r requirements-dev.txt
       - name: isort
         run: isort --recursive --diff
       - name: Black

+ 33 - 0
.github/workflows/tests.yml

@@ -0,0 +1,33 @@
+name: Python tests
+
+on: push
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.8]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements-first.txt
+          pip install -r requirements-dev.txt
+      - name: Test with pytest
+        run: pytest --cov=. --cov-config=.coveragerc --cov-report xml:coverage.xml
+      - name: Track master branch
+        run: git fetch --no-tags https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY +refs/heads/master:refs/remotes/origin/master
+      - name: SonarCloud scan
+        uses: sonarsource/sonarcloud-github-action@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

+ 3 - 1
.gitignore

@@ -2,4 +2,6 @@
 /.vscode/
 __pycache__/
 /config/
-*.zip
+*.zip
+/.coverage
+/coverage.xml

+ 2 - 2
custom_components/tuya_local/__init__.py

@@ -26,7 +26,7 @@ from .const import (
     CONF_TYPE,
     CONF_TYPE_DEHUMIDIFIER,
     CONF_TYPE_FAN,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     SCAN_INTERVAL,
     CONF_TYPE_AUTO,
 )
@@ -35,7 +35,7 @@ from .config_flow import ConfigFlowHandler
 
 _LOGGER = logging.getLogger(__name__)
 
-VERSION = "0.1.1"
+VERSION = "0.1.2"
 
 CONFIG_SCHEMA = vol.Schema(
     {DOMAIN: vol.All(cv.ensure_list, [vol.Schema(individual_config_schema())])},

+ 3 - 2
custom_components/tuya_local/climate.py

@@ -9,8 +9,9 @@ from .const import (
     CONF_TYPE_FAN,
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GPCV_HEATER,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_CLIMATE,
     CONF_TYPE_AUTO,
 )
@@ -33,7 +34,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         if discovery_info[CONF_TYPE] is None:
             raise ValueError(f"Unable to detect type for device {device.name}")
 
-    if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
+    if discovery_info[CONF_TYPE] == CONF_TYPE_GPPH_HEATER:
         data[CONF_CLIMATE] = GoldairHeater(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
         data[CONF_CLIMATE] = GoldairDehumidifier(device)

+ 2 - 2
custom_components/tuya_local/configuration.py

@@ -5,7 +5,7 @@ from .const import (
     CONF_DEVICE_ID,
     CONF_LOCAL_KEY,
     CONF_TYPE,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_DEHUMIDIFIER,
     CONF_TYPE_FAN,
     CONF_TYPE_GECO_HEATER,
@@ -27,7 +27,7 @@ INDIVIDUAL_CONFIG_SCHEMA_TEMPLATE = [
         "type": vol.In(
             [
                 CONF_TYPE_AUTO,
-                CONF_TYPE_HEATER,
+                CONF_TYPE_GPPH_HEATER,
                 CONF_TYPE_DEHUMIDIFIER,
                 CONF_TYPE_FAN,
                 CONF_TYPE_GECO_HEATER,

+ 1 - 1
custom_components/tuya_local/const.py

@@ -6,7 +6,7 @@ CONF_DEVICE_ID = "device_id"
 CONF_LOCAL_KEY = "local_key"
 CONF_TYPE = "type"
 CONF_TYPE_AUTO = "auto"
-CONF_TYPE_HEATER = "heater"
+CONF_TYPE_GPPH_HEATER = "heater"
 CONF_TYPE_DEHUMIDIFIER = "dehumidifier"
 CONF_TYPE_FAN = "fan"
 CONF_TYPE_GECO_HEATER = "geco_heater"

+ 6 - 2
custom_components/tuya_local/dehumidifier/climate.py

@@ -1,7 +1,11 @@
 """
 Goldair WiFi Dehumidifier device.
 """
-from homeassistant.components.climate import ClimateDevice
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
+
 from homeassistant.components.climate.const import (
     ATTR_FAN_MODE,
     ATTR_HUMIDITY,
@@ -39,7 +43,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY | SUPPORT_PRESET_MODE | SUPPORT_FAN_MODE
 
 
-class GoldairDehumidifier(ClimateDevice):
+class GoldairDehumidifier(ClimateEntity):
     """Representation of a Goldair WiFi dehumidifier."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/dehumidifier/light.py

@@ -1,15 +1,19 @@
 """
 Platform to control the LED display light on Goldair WiFi-connected dehumidifiers.
 """
+try:
+    from homeassistant.components.light import LightEntity
+except ImportError:
+    from homeassistant.components.light import Light as LightEntity
+
 from homeassistant.components.climate import ATTR_HVAC_MODE, HVAC_MODE_OFF
-from homeassistant.components.light import Light
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_DISPLAY_ON, HVAC_MODE_TO_DPS_MODE, PROPERTY_TO_DPS_ID
 
 
-class GoldairDehumidifierLedDisplayLight(Light):
+class GoldairDehumidifierLedDisplayLight(LightEntity):
     """Representation of a Goldair WiFi-connected dehumidifier LED display."""
 
     def __init__(self, device):

+ 7 - 2
custom_components/tuya_local/dehumidifier/lock.py

@@ -1,14 +1,19 @@
 """
 Platform to control the child lock on Goldair WiFi-connected dehumidifiers.
 """
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+try:
+    from homeassistant.components.lock import LockEntity
+except ImportError:
+    from homeassistant.components.lock import LockDevice as LockEntity
+
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 
 
-class GoldairDehumidifierChildLock(LockDevice):
+class GoldairDehumidifierChildLock(LockEntity):
     """Representation of a Goldair WiFi-connected dehumidifier child lock."""
 
     def __init__(self, device):

+ 2 - 2
custom_components/tuya_local/device.py

@@ -17,7 +17,7 @@ from .const import (
     CONF_TYPE_FAN,
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GPCV_HEATER,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
 )
 
@@ -94,7 +94,7 @@ class TuyaLocalDevice(object):
         if "8" in cached_state:
             return CONF_TYPE_FAN
         if "106" in cached_state:
-            return CONF_TYPE_HEATER
+            return CONF_TYPE_GPPH_HEATER
         if "7" in cached_state:
             return CONF_TYPE_GPCV_HEATER
         if "3" in cached_state:

+ 6 - 2
custom_components/tuya_local/fan/climate.py

@@ -1,7 +1,11 @@
 """
 Goldair WiFi Fan device.
 """
-from homeassistant.components.climate import ClimateDevice
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
+
 from homeassistant.components.climate.const import (
     ATTR_FAN_MODE,
     ATTR_HVAC_MODE,
@@ -25,7 +29,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE
 
 
-class GoldairFan(ClimateDevice):
+class GoldairFan(ClimateEntity):
     """Representation of a Goldair WiFi fan."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/fan/light.py

@@ -1,15 +1,19 @@
 """
 Platform to control the LED display light on Goldair WiFi-connected fans and panels.
 """
+try:
+    from homeassistant.components.light import LightEntity
+except ImportError:
+    from homeassistant.components.light import Light as LightEntity
+
 from homeassistant.components.climate import ATTR_HVAC_MODE, HVAC_MODE_OFF
-from homeassistant.components.light import Light
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_DISPLAY_ON, HVAC_MODE_TO_DPS_MODE, PROPERTY_TO_DPS_ID
 
 
-class GoldairFanLedDisplayLight(Light):
+class GoldairFanLedDisplayLight(LightEntity):
     """Representation of a Goldair WiFi-connected fan LED display."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/geco_heater/climate.py

@@ -1,7 +1,11 @@
 """
 Goldair GECO WiFi Heater device.
 """
-from homeassistant.components.climate import ClimateDevice
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
+
 from homeassistant.components.climate.const import (
     ATTR_HVAC_MODE,
     HVAC_MODE_HEAT,
@@ -20,7 +24,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
 
 
-class GoldairGECOHeater(ClimateDevice):
+class GoldairGECOHeater(ClimateEntity):
     """Representation of a Goldair GECO WiFi heater."""
 
     def __init__(self, device):

+ 7 - 2
custom_components/tuya_local/geco_heater/lock.py

@@ -1,14 +1,19 @@
 """
 Platform to control the child lock on Goldair GECO WiFi-connected heaters and panels.
 """
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+try:
+    from homeassistant.components.lock import LockEntity
+except ImportError:
+    from homeassistant.components.lock import LockDevice as LockEntity
+
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 
 
-class GoldairGECOHeaterChildLock(LockDevice):
+class GoldairGECOHeaterChildLock(LockEntity):
     """Representation of a Goldair GECO WiFi-connected heater child lock."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/gpcv_heater/climate.py

@@ -1,7 +1,11 @@
 """
 Goldair GPCV WiFi Heater device.
 """
-from homeassistant.components.climate import ClimateDevice
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
+
 from homeassistant.components.climate.const import (
     ATTR_HVAC_MODE,
     ATTR_PRESET_MODE,
@@ -23,7 +27,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
 
 
-class GoldairGPCVHeater(ClimateDevice):
+class GoldairGPCVHeater(ClimateEntity):
     """Representation of a Goldair GPCV WiFi heater."""
 
     def __init__(self, device):

+ 7 - 2
custom_components/tuya_local/gpcv_heater/lock.py

@@ -1,14 +1,19 @@
 """
 Platform to control the child lock on Goldair GPCV WiFi-connected heaters and panels.
 """
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+try:
+    from homeassistant.components.lock import LockEntity
+except ImportError:
+    from homeassistant.components.lock import LockDevice as LockEntity
+
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 
 
-class GoldairGPCVHeaterChildLock(LockDevice):
+class GoldairGPCVHeaterChildLock(LockEntity):
     """Representation of a Goldair GPCV WiFi-connected heater child lock."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/heater/climate.py

@@ -1,7 +1,11 @@
 """
 Goldair WiFi Heater device.
 """
-from homeassistant.components.climate import ClimateDevice
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
+
 from homeassistant.components.climate.const import (
     ATTR_HVAC_MODE,
     ATTR_PRESET_MODE,
@@ -34,7 +38,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE
 
 
-class GoldairHeater(ClimateDevice):
+class GoldairHeater(ClimateEntity):
     """Representation of a Goldair WiFi heater."""
 
     def __init__(self, device):

+ 6 - 2
custom_components/tuya_local/heater/light.py

@@ -1,15 +1,19 @@
 """
 Platform to control the LED display light on Goldair WiFi-connected heaters and panels.
 """
+try:
+    from homeassistant.components.light import LightEntity
+except ImportError:
+    from homeassistant.components.light import Light as LightEntity
+
 from homeassistant.components.climate import ATTR_HVAC_MODE, HVAC_MODE_OFF
-from homeassistant.components.light import Light
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_DISPLAY_ON, HVAC_MODE_TO_DPS_MODE, PROPERTY_TO_DPS_ID
 
 
-class GoldairHeaterLedDisplayLight(Light):
+class GoldairHeaterLedDisplayLight(LightEntity):
     """Representation of a Goldair WiFi-connected heater LED display."""
 
     def __init__(self, device):

+ 7 - 2
custom_components/tuya_local/heater/lock.py

@@ -1,14 +1,19 @@
 """
 Platform to control the child lock on Goldair WiFi-connected heaters and panels.
 """
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+try:
+    from homeassistant.components.lock import LockEntity
+except ImportError:
+    from homeassistant.components.lock import LockDevice as LockEntity
+
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 
 
-class GoldairHeaterChildLock(LockDevice):
+class GoldairHeaterChildLock(LockEntity):
     """Representation of a Goldair WiFi-connected heater child lock."""
 
     def __init__(self, device):

+ 5 - 2
custom_components/tuya_local/kogan_heater/climate.py

@@ -9,8 +9,11 @@ dps:
   7 = hvac_mode (boolean)
   8 = timer (integer) [supported for read only - use HA based timers]
 """
+try:
+    from homeassistant.components.climate import ClimateEntity
+except ImportError:
+    from homeassistant.components.climate import ClimateDevice as ClimateEntity
 
-from homeassistant.components.climate import ClimateDevice
 from homeassistant.components.climate.const import (
     ATTR_HVAC_MODE,
     ATTR_PRESET_MODE,
@@ -32,7 +35,7 @@ from .const import (
 SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
 
 
-class KoganHeater(ClimateDevice):
+class KoganHeater(ClimateEntity):
     """Representation of a Kogan WiFi heater."""
 
     def __init__(self, device):

+ 7 - 2
custom_components/tuya_local/kogan_heater/lock.py

@@ -1,14 +1,19 @@
 """
 Platform to control the child lock on Kogan WiFi-connected heaters and panels.
 """
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED, LockDevice
+try:
+    from homeassistant.components.lock import LockEntity
+except ImportError:
+    from homeassistant.components.lock import LockDevice as LockEntity
+
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 
 from ..device import TuyaLocalDevice
 from .const import ATTR_CHILD_LOCK, PROPERTY_TO_DPS_ID
 
 
-class KoganHeaterChildLock(LockDevice):
+class KoganHeaterChildLock(LockEntity):
     """Representation of a Kogan WiFi-connected heater child lock."""
 
     def __init__(self, device):

+ 2 - 2
custom_components/tuya_local/light.py

@@ -9,7 +9,7 @@ from .const import (
     CONF_TYPE_FAN,
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GPCV_HEATER,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
     CONF_DISPLAY_LIGHT,
     CONF_TYPE_AUTO,
@@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         if discovery_info[CONF_TYPE] is None:
             raise ValueError(f"Unable to detect type for device {device.name}")
 
-    if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
+    if discovery_info[CONF_TYPE] == CONF_TYPE_GPPH_HEATER:
         data[CONF_DISPLAY_LIGHT] = GoldairHeaterLedDisplayLight(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
         data[CONF_DISPLAY_LIGHT] = GoldairDehumidifierLedDisplayLight(device)

+ 2 - 2
custom_components/tuya_local/lock.py

@@ -9,7 +9,7 @@ from .const import (
     CONF_TYPE_FAN,
     CONF_TYPE_GECO_HEATER,
     CONF_TYPE_GPCV_HEATER,
-    CONF_TYPE_HEATER,
+    CONF_TYPE_GPPH_HEATER,
     CONF_TYPE_KOGAN_HEATER,
     CONF_CHILD_LOCK,
     CONF_TYPE_AUTO,
@@ -36,7 +36,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
         if discovery_info[CONF_TYPE] is None:
             raise ValueError(f"Unable to detect type for device {device.name}")
 
-    if discovery_info[CONF_TYPE] == CONF_TYPE_HEATER:
+    if discovery_info[CONF_TYPE] == CONF_TYPE_GPPH_HEATER:
         data[CONF_CHILD_LOCK] = GoldairHeaterChildLock(device)
     elif discovery_info[CONF_TYPE] == CONF_TYPE_DEHUMIDIFIER:
         data[CONF_CHILD_LOCK] = GoldairDehumidifierChildLock(device)

+ 7 - 0
requirements-dev.txt

@@ -0,0 +1,7 @@
+homeassistant~=0.110
+pycrypto~=2.6
+pytuya~=7.0
+pytest~=5.4
+pytest-cov~=2.9
+black~=19.10b0
+isort~=4.3

+ 1 - 0
requirements-first.txt

@@ -0,0 +1 @@
+pycrypto~=2.6

+ 2 - 2
requirements.txt

@@ -1,2 +1,2 @@
-black==19.*
-isort==4.*
+pycrypto~=2.6
+pytuya~=7.0

+ 6 - 0
sonar-project.properties

@@ -0,0 +1,6 @@
+sonar.organization=nikrolls
+sonar.projectKey=nikrolls_homeassistant-goldair-climate
+
+sonar.sources=./custom_components/goldair_climate
+sonar.tests=./tests
+sonar.python.coverage.reportPaths=/github/workspace/coverage.xml

+ 0 - 0
tests/__init__.py


+ 44 - 0
tests/const.py

@@ -0,0 +1,44 @@
+GPPH_HEATER_PAYLOAD = {
+    "1": False,
+    "2": 25,
+    "3": 17,
+    "4": "C",
+    "6": True,
+    "12": 0,
+    "101": "5",
+    "102": 0,
+    "103": False,
+    "104": True,
+    "105": "auto",
+    "106": 20,
+}
+
+GPCV_HEATER_PAYLOAD = {
+    "1": True,
+    "2": True,
+    "3": 30,
+    "4": 25,
+    "5": 0,
+    "6": 0,
+    "7": "Low",
+}
+
+GECO_HEATER_PAYLOAD = {"1": True, "2": True, "3": 30, "4": 25, "5": 0, "6": 0}
+
+DEHUMIDIFIER_PAYLOAD = {
+    "1": False,
+    "2": "0",
+    "4": 30,
+    "5": False,
+    "6": "1",
+    "7": False,
+    "11": 0,
+    "12": "0",
+    "101": False,
+    "102": False,
+    "103": 20,
+    "104": 78,
+    "105": False,
+}
+
+FAN_PAYLOAD = {"1": False, "2": "12", "3": "normal", "8": True, "11": "0", "101": False}

+ 82 - 0
tests/test_device.py

@@ -0,0 +1,82 @@
+from unittest import IsolatedAsyncioTestCase
+from unittest.mock import patch
+
+from custom_components.tuya_local.const import (
+    CONF_TYPE_DEHUMIDIFIER,
+    CONF_TYPE_FAN,
+    CONF_TYPE_GECO_HEATER,
+    CONF_TYPE_GPCV_HEATER,
+    CONF_TYPE_GPPH_HEATER,
+)
+from custom_components.tuya_local.device import TuyaLocalDevice
+
+from .const import (
+    DEHUMIDIFIER_PAYLOAD,
+    FAN_PAYLOAD,
+    GECO_HEATER_PAYLOAD,
+    GPCV_HEATER_PAYLOAD,
+    GPPH_HEATER_PAYLOAD,
+)
+
+
+class TestDevice(IsolatedAsyncioTestCase):
+    def setUp(self):
+        patcher = patch("pytuya.Device")
+        self.addCleanup(patcher.stop)
+        self.mock_api = patcher.start()
+        self.subject = TuyaLocalDevice(
+            "Some name", "some_dev_id", "some.ip.address", "some_local_key", None
+        )
+
+    def test_configures_pytuya_correctly(self):
+        self.mock_api.assert_called_once_with(
+            "some_dev_id", "some.ip.address", "some_local_key", "device"
+        )
+        self.assertIs(self.subject._api, self.mock_api())
+
+    def test_name(self):
+        """Returns the name given at instantiation."""
+        self.assertEqual("Some name", self.subject.name)
+
+    def test_unique_id(self):
+        """Returns the unique ID presented by the API class."""
+        self.assertIs(self.subject.unique_id, self.mock_api().id)
+
+    def test_device_info(self):
+        """Returns generic info plus the unique ID for categorisation."""
+        self.assertEqual(
+            self.subject.device_info,
+            {
+                "identifiers": {("tuya_local", self.mock_api().id)},
+                "name": "Some name",
+                "manufacturer": "Tuya",
+            },
+        )
+
+    async def test_detects_geco_heater_payload(self):
+        self.subject._cached_state = GECO_HEATER_PAYLOAD
+        self.assertEqual(
+            await self.subject.async_inferred_type(), CONF_TYPE_GECO_HEATER
+        )
+
+    async def test_detects_gpcv_heater_payload(self):
+        self.subject._cached_state = GPCV_HEATER_PAYLOAD
+        self.assertEqual(
+            await self.subject.async_inferred_type(), CONF_TYPE_GPCV_HEATER
+        )
+
+    async def test_detects_gpph_heater_payload(self):
+        self.subject._cached_state = GPPH_HEATER_PAYLOAD
+        self.assertEqual(
+            await self.subject.async_inferred_type(), CONF_TYPE_GPPH_HEATER
+        )
+
+    async def test_detects_dehumidifier_payload(self):
+        self.subject._cached_state = DEHUMIDIFIER_PAYLOAD
+        self.assertEqual(
+            await self.subject.async_inferred_type(), CONF_TYPE_DEHUMIDIFIER
+        )
+
+    async def test_detects_fan_payload(self):
+        self.subject._cached_state = FAN_PAYLOAD
+        self.assertEqual(await self.subject.async_inferred_type(), CONF_TYPE_FAN)