Nik Rolls 7 лет назад
Родитель
Сommit
637d305812
2 измененных файлов с 37 добавлено и 34 удалено
  1. 1 1
      README.md
  2. 36 33
      goldair_heater.py

+ 1 - 1
README.md

@@ -88,4 +88,4 @@ All I did was write some code. None of this would have been possible without:
 
 * [TarxBoy](https://github.com/TarxBoy)'s [investigation using codetheweb/tuyapi](https://github.com/codetheweb/tuyapi/issues/31) to figure out the correlation of the cryptic DPS states 
 * [sean6541](https://github.com/sean6541)'s [tuya-homeassistant](https://github.com/sean6541/tuya-homeassistant) library giving an example of integrating Tuya devices with Home Assistant
-* [clach04](https://github.com/clach04)`s [python-tuya](https://github.com/clach04/python-tuya) library
+* [clach04](https://github.com/clach04)'s [python-tuya](https://github.com/clach04/python-tuya) library

+ 36 - 33
goldair_heater.py

@@ -7,6 +7,8 @@ https://github.com/codetheweb/tuyapi/issues/31.
 """
 import logging
 import json
+from time import time
+from threading import Timer, Lock
 
 import voluptuous as vol
 from homeassistant.components.climate import (
@@ -17,7 +19,6 @@ from homeassistant.const import (
     CONF_NAME, CONF_HOST, STATE_ON, STATE_OFF, STATE_UNAVAILABLE, TEMP_CELSIUS
 )
 import homeassistant.helpers.config_validation as cv
-from threading import Lock
 
 REQUIREMENTS = ['pytuya==7.0']
 
@@ -43,7 +44,7 @@ ATTR_POWER_LEVEL = 'power_level'
 ATTR_TIMER_MINUTES = 'timer_minutes'
 ATTR_TIMER_ON = 'timer_on'
 ATTR_DISPLAY_ON = 'display_on'
-ATTR_USER_MODE = 'user_mode' # not sure what this does
+ATTR_USER_MODE = 'user_mode'  # not sure what this does
 ATTR_ECO_TARGET_TEMPERATURE = 'eco_' + ATTR_TARGET_TEMPERATURE
 
 GOLDAIR_PROPERTY_TO_DPS_ID = {
@@ -57,7 +58,7 @@ GOLDAIR_PROPERTY_TO_DPS_ID = {
     ATTR_TIMER_MINUTES: '102',
     ATTR_TIMER_ON: '103',
     ATTR_DISPLAY_ON: '104',
-    ATTR_USER_MODE: '105', # not sure what this does
+    ATTR_USER_MODE: '105',  # not sure what this does
     ATTR_ECO_TARGET_TEMPERATURE: '106'
 }
 
@@ -67,7 +68,7 @@ GOLDAIR_MODE_TO_DPS_MODE = {
     STATE_ANTI_FREEZE: 'AF'
 }
 GOLDAIR_POWER_LEVELS = ['stop', '1', '2', '3', '4', '5', 'auto']
-GOLDAIR_USER_MODES = ['auto', 'user'] # not sure what this does
+GOLDAIR_USER_MODES = ['auto', 'user']  # not sure what this does
 
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
     vol.Required(CONF_NAME): cv.string,
@@ -79,7 +80,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
     vol.Optional(CONF_DISPLAY_ON): cv.boolean
 })
 
-SUPPORT_FLAGS =  SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
+SUPPORT_FLAGS = SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
 
 
 def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -103,6 +104,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
         GoldairHeater(config.get(CONF_NAME), device)
     ])
 
+
 class GoldairHeater(ClimateDevice):
     """Representation of a Goldair WiFi heater."""
 
@@ -207,9 +209,9 @@ class GoldairHeater(ClimateDevice):
         self._device.refresh()
 
 
+# HA prefers this to be lazy-imported, however we can't do that because it needs to be subclassed
 import pytuya
-from time import time
-from threading import Timer
+
 
 class GoldairHeaterDevice(pytuya.Device):
     def __init__(self, dev_id, address, local_key):
@@ -222,7 +224,7 @@ class GoldairHeaterDevice(pytuya.Device):
             local_key (str): The encryption key.
         """
         super().__init__(dev_id, address, local_key, 'device')
-        
+
         self._fixed_properties = {}
         self._reset_cached_state()
 
@@ -270,16 +272,16 @@ class GoldairHeaterDevice(pytuya.Device):
     @property
     def max_target_temperature(self):
         return self._MAX_TARGET_TEMPERATURE
-    
+
     def set_target_temperature(self, target_temperature):
         target_temperature = int(round(target_temperature))
         if not self._MIN_TARGET_TEMPERATURE <= target_temperature <= self._MAX_TARGET_TEMPERATURE:
             raise ValueError(
                 f'Target temperature ({target_temperature}) must be between '
                 f'{self._MIN_TARGET_TEMPERATURE} and {self._MAX_TARGET_TEMPERATURE}'
-            ) 
+            )
         self._set_properties({ATTR_TARGET_TEMPERATURE: target_temperature})
-    
+
     @property
     def current_temperature(self):
         return self._get_cached_state()[ATTR_TEMPERATURE]
@@ -293,8 +295,8 @@ class GoldairHeaterDevice(pytuya.Device):
         return list(GOLDAIR_MODE_TO_DPS_MODE.keys())
 
     def set_operation_mode(self, new_mode):
-        if not new_mode in GOLDAIR_MODE_TO_DPS_MODE:
-            raise ValueError(f'Invalid mode: {new_mode}') 
+        if new_mode not in GOLDAIR_MODE_TO_DPS_MODE:
+            raise ValueError(f'Invalid mode: {new_mode}')
         self._set_properties({ATTR_OPERATION_MODE: new_mode})
 
     @property
@@ -316,7 +318,7 @@ class GoldairHeaterDevice(pytuya.Device):
         return self._get_cached_state()[ATTR_POWER_LEVEL]
 
     def set_power_level(self, new_level):
-        if not new_level in GOLDAIR_POWER_LEVELS:
+        if new_level not in GOLDAIR_POWER_LEVELS:
             raise ValueError(f'Invalid power level: {new_level}')
         self._set_properties({ATTR_POWER_LEVEL: new_level})
 
@@ -327,7 +329,7 @@ class GoldairHeaterDevice(pytuya.Device):
     @property
     def is_timer_on(self):
         return self._get_cached_state()[ATTR_TIMER_ON]
-    
+
     def start_timer(self, minutes):
         self._set_properties({
             ATTR_TIMER_ON: True,
@@ -340,10 +342,10 @@ class GoldairHeaterDevice(pytuya.Device):
     @property
     def is_display_on(self):
         return self._get_cached_state()[ATTR_DISPLAY_ON]
-    
+
     def turn_display_on(self):
         self._set_properties({ATTR_DISPLAY_ON: True})
-    
+
     def turn_display_off(self):
         self._set_properties({ATTR_DISPLAY_ON: False})
 
@@ -352,16 +354,16 @@ class GoldairHeaterDevice(pytuya.Device):
         return self._get_cached_state()[ATTR_USER_MODE]
 
     def set_user_mode(self, new_mode):
-        if not new_mode in GOLDAIR_USER_MODES:
+        if new_mode not in GOLDAIR_USER_MODES:
             raise ValueError(f'Invalid user mode: {new_mode}')
         self._set_properties({ATTR_USER_MODE: new_mode})
 
     @property
     def eco_target_temperature(self):
         return self._get_cached_state()[ATTR_ECO_TARGET_TEMPERATURE]
-    
+
     def set_eco_target_temperature(self, eco_target_temperature):
-        self._set_properties({ATTR_ECO_TARGET_TEMPERATURE})
+        self._set_properties({ATTR_ECO_TARGET_TEMPERATURE: eco_target_temperature})
 
     def set_fixed_properties(self, fixed_properties):
         self._fixed_properties = fixed_properties
@@ -373,7 +375,7 @@ class GoldairHeaterDevice(pytuya.Device):
         cached_state = self._get_cached_state()
         if now - cached_state['updated_at'] >= self._CACHE_TIMEOUT:
             self._retry_on_failed_connection(lambda: self._refresh_cached_state(), 'Failed to refresh device state.')
-    
+
     def _reset_cached_state(self):
         self._cached_state = {
             ATTR_ON: None,
@@ -398,7 +400,7 @@ class GoldairHeaterDevice(pytuya.Device):
         _LOGGER.info(f'refreshed device state: {json.dumps(new_state)}')
         _LOGGER.debug(f'new cache state: {json.dumps(self._cached_state)}')
         _LOGGER.debug(f'new cache state (including pending properties): {json.dumps(self._get_cached_state())}')
-    
+
     def _set_properties(self, properties):
         if len(properties) == 0:
             return
@@ -409,14 +411,14 @@ class GoldairHeaterDevice(pytuya.Device):
     def _add_properties_to_pending_updates(self, properties):
         now = time()
         properties = {**properties, **self._fixed_properties}
-        
+
         pending_updates = self._get_pending_updates()
         for key, value in properties.items():
             pending_updates[key] = {
                 'value': value,
                 'updated_at': now
             }
-        
+
         _LOGGER.debug(f'new pending updates: {json.dumps(self._pending_updates)}')
 
     def _debounce_sending_updates(self):
@@ -426,10 +428,10 @@ class GoldairHeaterDevice(pytuya.Device):
             pass
         self._debounce = Timer(1, self._send_pending_updates)
         self._debounce.start()
-    
+
     def _send_pending_updates(self):
         pending_properties = self._get_pending_properties()
-        new_state = self._generate_dps_payload_for_properties(pending_properties)
+        new_state = GoldairHeaterDevice._generate_dps_payload_for_properties(pending_properties)
         payload = self.generate_payload('set', new_state)
 
         _LOGGER.debug(f'sending updated properties: {json.dumps(pending_properties)}')
@@ -443,9 +445,8 @@ class GoldairHeaterDevice(pytuya.Device):
             self._send_receive(payload)
             now = time()
             pending_updates = self._get_pending_updates()
-            for key, value in properties.items():
-                if key in pending_updates:
-                    pending_updates[key]['updated_at'] = now
+            for key, value in pending_updates.items():
+                pending_updates[key]['updated_at'] = now
         finally:
             self._lock.release()
 
@@ -469,7 +470,7 @@ class GoldairHeaterDevice(pytuya.Device):
     def _get_pending_updates(self):
         now = time()
         self._pending_updates = {key: value for key, value in self._pending_updates.items()
-                                            if now - value['updated_at'] < self._FAKE_IT_TIL_YOU_MAKE_IT_TIMEOUT}
+                                 if now - value['updated_at'] < self._FAKE_IT_TIL_YOU_MAKE_IT_TIMEOUT}
         return self._pending_updates
 
     def _update_cached_state_from_dps(self, dps):
@@ -479,12 +480,13 @@ class GoldairHeaterDevice(pytuya.Device):
             if dps_id in dps:
                 value = dps[dps_id]
                 if dps_id == GOLDAIR_PROPERTY_TO_DPS_ID[ATTR_OPERATION_MODE]:
-                    self._cached_state[key] = self._get_key_for_value(GOLDAIR_MODE_TO_DPS_MODE, value)
+                    self._cached_state[key] = GoldairHeaterDevice._get_key_for_value(GOLDAIR_MODE_TO_DPS_MODE, value)
                 else:
                     self._cached_state[key] = value
                 self._cached_state['updated_at'] = now
 
-    def _generate_dps_payload_for_properties(self, properties):
+    @staticmethod
+    def _generate_dps_payload_for_properties(properties):
         dps = {}
 
         for key, dps_id in GOLDAIR_PROPERTY_TO_DPS_ID.items():
@@ -497,7 +499,8 @@ class GoldairHeaterDevice(pytuya.Device):
 
         return dps
 
-    def _get_key_for_value(self, obj, value):
+    @staticmethod
+    def _get_key_for_value(obj, value):
         keys = list(obj.keys())
         values = list(obj.values())
         return keys[values.index(value)]