diff --git a/custom_components/localtuya/cover.py b/custom_components/localtuya/cover.py index 7078343..d6bdceb 100644 --- a/custom_components/localtuya/cover.py +++ b/custom_components/localtuya/cover.py @@ -43,8 +43,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'localtuyacover' -REQUIREMENTS = ['pytuya>=8.0.0'] - CONF_DEVICE_ID = 'device_id' CONF_LOCAL_KEY = 'local_key' CONF_PROTOCOL_VERSION = 'protocol_version' @@ -84,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): #_LOGGER.info("conf_STOP_cmd is %s", config.get(CONF_STOP_CMD)) covers = [] - pytuyadevice = pytuya.PytuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) + pytuyadevice = pytuya.TuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) pytuyadevice.set_version(float(config.get(CONF_PROTOCOL_VERSION))) dps = {} dps[config.get(CONF_ID)]=None @@ -111,7 +109,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class TuyaCache: - """Cache wrapper for pytuya.PytuyaDevice""" + """Cache wrapper for pytuya.TuyaDevice""" def __init__(self, device): """Initialize the cache.""" @@ -139,15 +137,15 @@ class TuyaCache: # return None raise ConnectionError("Failed to update status .") - def set_dps(self, state, switchid): + def set_dps(self, state, dps_index): #_LOGGER.info("running def set_dps from cover") """Change the Tuya switch status and clear the cache.""" self._cached_status = '' self._cached_status_time = 0 for i in range(5): try: - #_LOGGER.info("Running a try from def set_dps from cover where state=%s and switchid=%s", state, switchid) - return self._device.set_dps(state, switchid) + #_LOGGER.info("Running a try from def set_dps from cover where state=%s and dps_index=%s", state, dps_index) + return self._device.set_dps(state, dps_index) except Exception: print('Failed to set status of device [{}]'.format(self._device.address)) if i+1 == 3: @@ -185,7 +183,7 @@ class LocaltuyaCover(CoverEntity): self._open_cmd = open_cmd self._close_cmd = close_cmd self._stop_cmd = stop_cmd - #_LOGGER.info("running def __init__ of TuyaDevice(PytuyaDevice) from cover.py with self=%s device=%s name=%s friendly_name=%s icon=%s switchid=%s open_cmd=%s close_cmd=%s stop_cmd=%s", self, device, name, friendly_name, icon, switchid, open_cmd, close_cmd, stop_cmd) + #_LOGGER.info("running def __init__ of LocaltuyaCover(CoverEntity) with self=%s device=%s name=%s friendly_name=%s icon=%s switchid=%s open_cmd=%s close_cmd=%s stop_cmd=%s", self, device, name, friendly_name, icon, switchid, open_cmd, close_cmd, stop_cmd) print('Initialized tuya cover [{}] with switch status [{}] and state [{}]'.format(self._name, self._status, self._state)) @property diff --git a/custom_components/localtuya/fan.py b/custom_components/localtuya/fan.py index 445c79c..2b08fba 100644 --- a/custom_components/localtuya/fan.py +++ b/custom_components/localtuya/fan.py @@ -32,8 +32,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'localtuyafan' -REQUIREMENTS = ['pytuya>=8.0.0'] - CONF_DEVICE_ID = 'device_id' CONF_LOCAL_KEY = 'local_key' CONF_PROTOCOL_VERSION = 'protocol_version' @@ -58,7 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up of the Tuya switch.""" from . import pytuya fans = [] - pytuyadevice = pytuya.PytuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) + pytuyadevice = pytuya.TuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) pytuyadevice.set_version(float(config.get(CONF_PROTOCOL_VERSION))) _LOGGER.debug("localtuya fan: setup_platform: %s", pytuyadevice) diff --git a/custom_components/localtuya/light.py b/custom_components/localtuya/light.py index dd07d20..dc7c0bf 100644 --- a/custom_components/localtuya/light.py +++ b/custom_components/localtuya/light.py @@ -31,8 +31,6 @@ from homeassistant.components.light import ( from homeassistant.util import color as colorutil import socket -REQUIREMENTS = ['pytuya>=8.0.0'] - CONF_DEVICE_ID = 'device_id' CONF_LOCAL_KEY = 'local_key' CONF_PROTOCOL_VERSION = 'protocol_version' @@ -62,7 +60,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): from . import pytuya lights = [] - pytuyadevice = pytuya.PytuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) + pytuyadevice = pytuya.TuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) pytuyadevice.set_version(float(config.get(CONF_PROTOCOL_VERSION))) bulb_device = TuyaCache(pytuyadevice) @@ -79,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(lights) class TuyaCache: - """Cache wrapper for pytuya.PytuyaDevices""" + """Cache wrapper for pytuya.TuyaDevices""" def __init__(self, device): """Initialize the cache.""" @@ -105,13 +103,13 @@ class TuyaCache: log.warn( "Failed to get status after {} tries".format(UPDATE_RETRY_LIMIT)) - def set_dps(self, state, switchid): + def set_dps(self, state, dps_index): """Change the Tuya switch status and clear the cache.""" self._cached_status = '' self._cached_status_time = 0 for _ in range(UPDATE_RETRY_LIMIT): try: - return self._device.set_dps(state, switchid) + return self._device.set_dps(state, dps_index) except ConnectionError: pass except socket.timeout: @@ -135,75 +133,16 @@ class TuyaCache: def cached_status(self): return self._cached_status - def support_color(self): - return self._device.support_color() - - def support_color_temp(self): - return self._device.support_color_temp() - - def brightness(self): - for _ in range(UPDATE_RETRY_LIMIT): - try: - return self._device.brightness() - except ConnectionError: - pass - except KeyError: - return "999" - except socket.timeout: - pass - log.warn( - "Failed to get brightness after {} tries".format(UPDATE_RETRY_LIMIT)) - - def color_temp(self): - for _ in range(UPDATE_RETRY_LIMIT): - try: - return self._device.colourtemp() - except ConnectionError: - pass - except KeyError: - return "999" - except socket.timeout: - pass - log.warn( - "Failed to get color temp after {} tries".format(UPDATE_RETRY_LIMIT)) - - def set_brightness(self, brightness): - for _ in range(UPDATE_RETRY_LIMIT): - try: - return self._device.set_brightness(brightness) - except ConnectionError: - pass - except KeyError: - pass - except socket.timeout: - pass - log.warn( - "Failed to set brightness after {} tries".format(UPDATE_RETRY_LIMIT)) - - def set_color_temp(self, color_temp): - for _ in range(UPDATE_RETRY_LIMIT): - try: - return self._device.set_colourtemp(color_temp) - except ConnectionError: - pass - except KeyError: - pass - except socket.timeout: - pass - log.warn( - "Failed to set color temp after {} tries".format(UPDATE_RETRY_LIMIT)) - - def state(self): + def state(self): self._device.state(); - def turn_on(self): - self._device.turn_on(); - - def turn_off(self): - self._device.turn_off(); - class LocaltuyaLight(LightEntity): """Representation of a Tuya switch.""" + DPS_INDEX_ON = '1' + DPS_INDEX_MODE = '2' + DPS_INDEX_BRIGHTNESS = '3' + DPS_INDEX_COLOURTEMP = '4' + DPS_INDEX_COLOUR = '5' def __init__(self, device, name, friendly_name, icon, bulbid): """Initialize the Tuya switch.""" @@ -301,7 +240,7 @@ class LocaltuyaLight(LightEntity): converted_brightness = int(kwargs[ATTR_BRIGHTNESS]) if converted_brightness <= 25: converted_brightness = 25 - self._device.set_brightness(converted_brightness) + self.set_brightness(converted_brightness) if ATTR_HS_COLOR in kwargs: raise ValueError(" TODO implement RGB from HS") if ATTR_COLOR_TEMP in kwargs: @@ -320,3 +259,86 @@ class LocaltuyaLight(LightEntity): supports = supports | SUPPORT_COLOR #supports = supports | SUPPORT_COLOR_TEMP return supports + + def support_color(self): + return supported_features() & SUPPORT_COLOR + + def support_color_temp(self): + return supported_features() & SUPPORT_COLOR_TEMP + + def color_temp(self): + for _ in range(UPDATE_RETRY_LIMIT): + try: + return self._state[self.DPS][self.DPS_INDEX_COLOURTEMP] + except ConnectionError: + pass + except KeyError: + return "999" + except socket.timeout: + pass + log.warn( + "Failed to get color temp after {} tries".format(UPDATE_RETRY_LIMIT)) + + def set_color_temp(self, color_temp): + for _ in range(UPDATE_RETRY_LIMIT): + try: + if not 0 <= colourtemp <= 255: + raise ValueError("The colour temperature needs to be between 0 and 255.") + + self._device.set_dps(colourtemp, self.DPS_INDEX_COLOURTEMP) + except ConnectionError: + pass + except KeyError: + pass + except socket.timeout: + pass + log.warn( + "Failed to set color temp after {} tries".format(UPDATE_RETRY_LIMIT)) + + def set_brightness(self, brightness): + for _ in range(UPDATE_RETRY_LIMIT): + try: + if not 25 <= brightness <= 255: + raise ValueError("The brightness needs to be between 25 and 255.") + + self._device.set_dps(brightness, self.DPS_INDEX_BRIGHTNESS) + except ConnectionError: + pass + except KeyError: + pass + except socket.timeout: + pass + log.warn( + "Failed to set brightness after {} tries".format(UPDATE_RETRY_LIMIT)) + + + @staticmethod + def _hexvalue_to_rgb(hexvalue): + """ + Converts the hexvalue used by tuya for colour representation into + an RGB value. + + Args: + hexvalue(string): The hex representation generated by BulbDevice._rgb_to_hexvalue() + """ + r = int(hexvalue[0:2], 16) + g = int(hexvalue[2:4], 16) + b = int(hexvalue[4:6], 16) + + return (r, g, b) + + @staticmethod + def _hexvalue_to_hsv(hexvalue): + """ + Converts the hexvalue used by tuya for colour representation into + an HSV value. + + Args: + hexvalue(string): The hex representation generated by BulbDevice._rgb_to_hexvalue() + """ + h = int(hexvalue[7:10], 16) / 360 + s = int(hexvalue[10:12], 16) / 255 + v = int(hexvalue[12:14], 16) / 255 + + return (h, s, v) + diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 1fd57a3..2e67559 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -11,7 +11,7 @@ For more information see https://github.com/clach04/python-tuya Classes - PytuyaDevice(dev_id, address, local_key=None) + TuyaDevice(dev_id, address, local_key=None) dev_id (str): Device ID e.g. 01234567891234567890 address (str): Device Network IP Address e.g. 10.0.1.99 local_key (str, optional): The encryption key. Defaults to None. @@ -20,8 +20,7 @@ json = status() # returns json payload set_version(version) # 3.1 [default] or 3.3 set_dpsUsed(dpsUsed) - set_dps(on, switch=1) # Set the of the device to 'on' or 'off' (bool) - set_value(index, value) # Set int value of any index. + set_dps(on, dps_index) # Set int value of any dps index. set_timer(num_secs): @@ -172,12 +171,7 @@ payload_dict = { } } -#class PytuyaDevice(XenonDevice): -# def __init__(self, dev_id, address, local_key=None, dev_type=None): -# super(PytuyaDevice, self).__init__(dev_id, address, local_key, dev_type) - -#class XenonDevice(object): -class PytuyaDevice(object): +class TuyaDevice(object): def __init__(self, dev_id, address, local_key=None, connection_timeout=10): """ Represents a Tuya device. @@ -365,20 +359,20 @@ class PytuyaDevice(object): return result - def set_dps(self, value, dpsIndex): + def set_dps(self, value, dps_index): """ - Set int value of any index. + Set value (may be any type: bool, int or string) of any dps index. Args: - dpsIndex(int): dps index to set + dps_index(int): dps index to set value: new value for the dps index """ # open device, send request, then close connection - if isinstance(dpsIndex, int): - dpsIndex = str(dpsIndex) # index and payload is a string + if isinstance(dps_index, int): + dps_index = str(dps_index) # index and payload is a string payload = self.generate_payload(SET, { - dpsIndex: value}) + dps_index: value}) data = self._send_receive(payload) log.debug('set_dps received data=%r', data) diff --git a/custom_components/localtuya/switch.py b/custom_components/localtuya/switch.py index dd7cf78..1df12e1 100644 --- a/custom_components/localtuya/switch.py +++ b/custom_components/localtuya/switch.py @@ -35,8 +35,6 @@ from threading import Lock _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pytuya>=8.0.0'] - CONF_DEVICE_ID = 'device_id' CONF_LOCAL_KEY = 'local_key' CONF_PROTOCOL_VERSION = 'protocol_version' @@ -84,7 +82,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices = config.get(CONF_SWITCHES) switches = [] - pytuyadevice = pytuya.PytuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) + pytuyadevice = pytuya.TuyaDevice(config.get(CONF_DEVICE_ID), config.get(CONF_HOST), config.get(CONF_LOCAL_KEY)) pytuyadevice.set_version(float(config.get(CONF_PROTOCOL_VERSION))) if len(devices) > 0: @@ -145,7 +143,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(switches, True) class TuyaCache: - """Cache wrapper for pytuya.PytuyaDevice""" + """Cache wrapper for pytuya.TuyaDevice""" def __init__(self, device): """Initialize the cache.""" @@ -172,13 +170,13 @@ class TuyaCache: # return None raise ConnectionError("Failed to update status .") - def set_dps(self, state, switchid): + def set_dps(self, state, dps_index): """Change the Tuya switch status and clear the cache.""" self._cached_status = '' self._cached_status_time = 0 for i in range(5): try: - return self._device.set_dps(state, switchid) + return self._device.set_dps(state, dps_index) except Exception: print('Failed to set status of device [{}]'.format(self._device.address)) if i+1 == 3: