Fix pydocstyle issues and enable in tox
This commit is contained in:
@@ -140,7 +140,7 @@ def get_entity_config(config_entry, dps_id):
|
|||||||
|
|
||||||
|
|
||||||
class TuyaDevice:
|
class TuyaDevice:
|
||||||
"""Cache wrapper for pytuya.TuyaInterface"""
|
"""Cache wrapper for pytuya.TuyaInterface."""
|
||||||
|
|
||||||
def __init__(self, interface, friendly_name):
|
def __init__(self, interface, friendly_name):
|
||||||
"""Initialize the cache."""
|
"""Initialize the cache."""
|
||||||
@@ -226,6 +226,7 @@ class LocalTuyaEntity(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
|
"""Return device information for the device registry."""
|
||||||
return {
|
return {
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
# Serial numbers are unique identifiers within a specific domain
|
# Serial numbers are unique identifiers within a specific domain
|
||||||
|
@@ -78,7 +78,7 @@ def flow_schema(dps):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Setup a Tuya cover based on a config entry."""
|
"""Set up a Tuya cover based on a config entry."""
|
||||||
tuyainterface, entities_to_setup = prepare_setup_entities(
|
tuyainterface, entities_to_setup = prepare_setup_entities(
|
||||||
config_entry, DOMAIN
|
config_entry, DOMAIN
|
||||||
)
|
)
|
||||||
@@ -103,7 +103,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
||||||
"""Tuya cover devices."""
|
"""Tuya cover device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -112,6 +112,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
switchid,
|
switchid,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
"""Initialize a new LocaltuyaCover."""
|
||||||
#_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)
|
#_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)
|
||||||
super().__init__(device, config_entry, switchid, **kwargs)
|
super().__init__(device, config_entry, switchid, **kwargs)
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -137,6 +138,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cover_position(self):
|
def current_cover_position(self):
|
||||||
|
"""Return current cover position in percent."""
|
||||||
# self.update()
|
# self.update()
|
||||||
# state = self._state
|
# state = self._state
|
||||||
# _LOGGER.info("curr_pos() : %i", self._position)
|
# _LOGGER.info("curr_pos() : %i", self._position)
|
||||||
@@ -145,6 +147,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_opening(self):
|
def is_opening(self):
|
||||||
|
"""Return if cover is opening."""
|
||||||
# self.update()
|
# self.update()
|
||||||
state = self._state
|
state = self._state
|
||||||
# print('is_opening() : state [{}]'.format(state))
|
# print('is_opening() : state [{}]'.format(state))
|
||||||
@@ -154,6 +157,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closing(self):
|
def is_closing(self):
|
||||||
|
"""Return if cover is closing."""
|
||||||
# self.update()
|
# self.update()
|
||||||
state = self._state
|
state = self._state
|
||||||
# print('is_closing() : state [{}]'.format(state))
|
# print('is_closing() : state [{}]'.format(state))
|
||||||
@@ -177,7 +181,6 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
def set_cover_position(self, **kwargs):
|
def set_cover_position(self, **kwargs):
|
||||||
# _LOGGER.info("running set_cover_position from cover")
|
# _LOGGER.info("running set_cover_position from cover")
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
|
|
||||||
newpos = float(kwargs["position"])
|
newpos = float(kwargs["position"])
|
||||||
# _LOGGER.info("Set new pos: %f", newpos)
|
# _LOGGER.info("Set new pos: %f", newpos)
|
||||||
|
|
||||||
@@ -212,5 +215,5 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
self._device.set_dps(self._config[CONF_STOP_CMD], self._dps_id)
|
self._device.set_dps(self._config[CONF_STOP_CMD], self._dps_id)
|
||||||
|
|
||||||
def status_updated(self):
|
def status_updated(self):
|
||||||
"""Device status was updated. """
|
"""Device status was updated."""
|
||||||
self._state = self.dps(self._dps_id)
|
self._state = self.dps(self._dps_id)
|
||||||
|
@@ -72,6 +72,7 @@ async def discover(timeout, loop):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
"""Run discovery and print result."""
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
res = loop.run_until_complete(discover(5, loop))
|
res = loop.run_until_complete(discover(5, loop))
|
||||||
print(res)
|
print(res)
|
||||||
|
@@ -49,7 +49,7 @@ def flow_schema(dps):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Setup a Tuya fan based on a config entry."""
|
"""Set up a Tuya fan based on a config entry."""
|
||||||
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
||||||
if not entities_to_setup:
|
if not entities_to_setup:
|
||||||
return
|
return
|
||||||
|
@@ -59,7 +59,7 @@ def flow_schema(dps):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Setup a Tuya light based on a config entry."""
|
"""Set up a Tuya light based on a config entry."""
|
||||||
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
||||||
if not entities_to_setup:
|
if not entities_to_setup:
|
||||||
return
|
return
|
||||||
|
@@ -1,37 +1,38 @@
|
|||||||
# PyTuya Module
|
# PyTuya Module
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Python module to interface with Tuya WiFi smart devices
|
Python module to interface with Tuya WiFi smart devices.
|
||||||
Mostly derived from Shenzhen Xenon ESP8266MOD WiFi smart devices
|
|
||||||
E.g. https://wikidevi.com/wiki/Xenon_SM-PW701U
|
|
||||||
|
|
||||||
Author: clach04
|
Mostly derived from Shenzhen Xenon ESP8266MOD WiFi smart devices
|
||||||
Maintained by: rospogrigio
|
E.g. https://wikidevi.com/wiki/Xenon_SM-PW701U
|
||||||
|
|
||||||
For more information see https://github.com/clach04/python-tuya
|
Author: clach04
|
||||||
|
Maintained by: rospogrigio
|
||||||
|
|
||||||
Classes
|
For more information see https://github.com/clach04/python-tuya
|
||||||
TuyaInterface(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.
|
|
||||||
|
|
||||||
Functions
|
Classes
|
||||||
json = status() # returns json payload
|
TuyaInterface(dev_id, address, local_key=None)
|
||||||
set_version(version) # 3.1 [default] or 3.3
|
dev_id (str): Device ID e.g. 01234567891234567890
|
||||||
detect_available_dps() # returns a list of available dps provided by the device
|
address (str): Device Network IP Address e.g. 10.0.1.99
|
||||||
add_dps_to_request(dps_index) # adds dps_index to the list of dps used by the device (to be queried in the payload)
|
local_key (str, optional): The encryption key. Defaults to None.
|
||||||
set_dps(on, dps_index) # Set value of any dps index.
|
|
||||||
set_timer(num_secs):
|
Functions
|
||||||
|
json = status() # returns json payload
|
||||||
|
set_version(version) # 3.1 [default] or 3.3
|
||||||
|
detect_available_dps() # returns a list of available dps provided by the device
|
||||||
|
add_dps_to_request(dps_index) # adds dps_index to the list of dps used by the device (to be queried in the payload)
|
||||||
|
set_dps(on, dps_index) # Set value of any dps index.
|
||||||
|
set_timer(num_secs):
|
||||||
|
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
* TuyaAPI https://github.com/codetheweb/tuyapi by codetheweb and blackrozes
|
* TuyaAPI https://github.com/codetheweb/tuyapi by codetheweb and blackrozes
|
||||||
For protocol reverse engineering
|
For protocol reverse engineering
|
||||||
* PyTuya https://github.com/clach04/python-tuya by clach04
|
* PyTuya https://github.com/clach04/python-tuya by clach04
|
||||||
The origin of this python module (now abandoned)
|
The origin of this python module (now abandoned)
|
||||||
* LocalTuya https://github.com/rospogrigio/localtuya-homeassistant by rospogrigio
|
* LocalTuya https://github.com/rospogrigio/localtuya-homeassistant by rospogrigio
|
||||||
Updated pytuya to support devices with Device IDs of 22 characters
|
Updated pytuya to support devices with Device IDs of 22 characters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
@@ -80,12 +81,16 @@ PROTOCOL_VERSION_BYTES_33 = b"3.3"
|
|||||||
IS_PY2 = sys.version_info[0] == 2
|
IS_PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
|
||||||
class AESCipher(object):
|
class AESCipher:
|
||||||
|
"""Cipher module for Tuya communication."""
|
||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
|
"""Initialize a new AESCipher."""
|
||||||
self.bs = 16
|
self.bs = 16
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
||||||
def encrypt(self, raw, use_base64=True):
|
def encrypt(self, raw, use_base64=True):
|
||||||
|
"""Encrypt data to be sent to device."""
|
||||||
if Crypto:
|
if Crypto:
|
||||||
raw = self._pad(raw)
|
raw = self._pad(raw)
|
||||||
cipher = AES.new(self.key, mode=AES.MODE_ECB)
|
cipher = AES.new(self.key, mode=AES.MODE_ECB)
|
||||||
@@ -104,6 +109,7 @@ class AESCipher(object):
|
|||||||
return crypted_text
|
return crypted_text
|
||||||
|
|
||||||
def decrypt(self, enc, use_base64=True):
|
def decrypt(self, enc, use_base64=True):
|
||||||
|
"""Decrypt data from device."""
|
||||||
if use_base64:
|
if use_base64:
|
||||||
enc = base64.b64decode(enc)
|
enc = base64.b64decode(enc)
|
||||||
# print('enc (%d) %r' % (len(enc), enc))
|
# print('enc (%d) %r' % (len(enc), enc))
|
||||||
@@ -134,6 +140,7 @@ class AESCipher(object):
|
|||||||
|
|
||||||
|
|
||||||
def bin2hex(x, pretty=False):
|
def bin2hex(x, pretty=False):
|
||||||
|
"""Convert binary data to hex string."""
|
||||||
if pretty:
|
if pretty:
|
||||||
space = " "
|
space = " "
|
||||||
else:
|
else:
|
||||||
@@ -146,6 +153,7 @@ def bin2hex(x, pretty=False):
|
|||||||
|
|
||||||
|
|
||||||
def hex2bin(x):
|
def hex2bin(x):
|
||||||
|
"""Convert hex string to binary."""
|
||||||
if IS_PY2:
|
if IS_PY2:
|
||||||
return x.decode("hex")
|
return x.decode("hex")
|
||||||
else:
|
else:
|
||||||
@@ -171,12 +179,14 @@ payload_dict = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TuyaInterface(object):
|
class TuyaInterface:
|
||||||
|
"""Represent a Tuya device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, dev_id, address, local_key, protocol_version, connection_timeout=10
|
self, dev_id, address, local_key, protocol_version, connection_timeout=10
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Represents a Tuya device.
|
Initialize a new TuyaInterface.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dev_id (str): The device id.
|
dev_id (str): The device id.
|
||||||
@@ -186,7 +196,6 @@ class TuyaInterface(object):
|
|||||||
Attributes:
|
Attributes:
|
||||||
port (int): The port to connect to.
|
port (int): The port to connect to.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.id = dev_id
|
self.id = dev_id
|
||||||
self.address = address
|
self.address = address
|
||||||
self.local_key = local_key.encode("latin1")
|
self.local_key = local_key.encode("latin1")
|
||||||
@@ -198,6 +207,7 @@ class TuyaInterface(object):
|
|||||||
self.port = 6668 # default - do not expect caller to pass in
|
self.port = 6668 # default - do not expect caller to pass in
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
"""Return internal string representation of object."""
|
||||||
return "%r" % ((self.id, self.address),) # FIXME can do better than this
|
return "%r" % ((self.id, self.address),) # FIXME can do better than this
|
||||||
|
|
||||||
def _send_receive(self, payload):
|
def _send_receive(self, payload):
|
||||||
@@ -239,6 +249,7 @@ class TuyaInterface(object):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def detect_available_dps(self):
|
def detect_available_dps(self):
|
||||||
|
"""Return which datapoints are supported by the device."""
|
||||||
# type_0d devices need a sort of bruteforce querying in order to detect the list of available dps
|
# type_0d devices need a sort of bruteforce querying in order to detect the list of available dps
|
||||||
# experience shows that the dps available are usually in the ranges [1-25] and [100-110]
|
# experience shows that the dps available are usually in the ranges [1-25] and [100-110]
|
||||||
# need to split the bruteforcing in different steps due to request payload limitation (max. length = 255)
|
# need to split the bruteforcing in different steps due to request payload limitation (max. length = 255)
|
||||||
@@ -288,6 +299,7 @@ class TuyaInterface(object):
|
|||||||
return detected_dps
|
return detected_dps
|
||||||
|
|
||||||
def add_dps_to_request(self, dps_index):
|
def add_dps_to_request(self, dps_index):
|
||||||
|
"""Add a datapoint (DP) to be included in requests."""
|
||||||
if isinstance(dps_index, int):
|
if isinstance(dps_index, int):
|
||||||
self.dps_to_request[str(dps_index)] = None
|
self.dps_to_request[str(dps_index)] = None
|
||||||
else:
|
else:
|
||||||
@@ -393,6 +405,7 @@ class TuyaInterface(object):
|
|||||||
return buffer
|
return buffer
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
|
"""Return device status."""
|
||||||
log.debug("status() entry (dev_type is %s)", self.dev_type)
|
log.debug("status() entry (dev_type is %s)", self.dev_type)
|
||||||
# open device, send request, then close connection
|
# open device, send request, then close connection
|
||||||
payload = self.generate_payload("status")
|
payload = self.generate_payload("status")
|
||||||
|
@@ -56,7 +56,7 @@ def flow_schema(dps):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Setup a Tuya sensor based on a config entry."""
|
"""Set up a Tuya sensor based on a config entry."""
|
||||||
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
||||||
if not entities_to_setup:
|
if not entities_to_setup:
|
||||||
return
|
return
|
||||||
|
@@ -94,7 +94,7 @@ def flow_schema(dps):
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Setup a Tuya switch based on a config entry."""
|
"""Set up a Tuya switch based on a config entry."""
|
||||||
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
|
||||||
if not entities_to_setup:
|
if not entities_to_setup:
|
||||||
return
|
return
|
||||||
@@ -152,6 +152,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
|
"""Return device state attributes."""
|
||||||
attrs = {}
|
attrs = {}
|
||||||
if self._config.get(CONF_CURRENT, "-1") != "-1":
|
if self._config.get(CONF_CURRENT, "-1") != "-1":
|
||||||
attrs[ATTR_CURRENT] = self.dps(self._config[CONF_CURRENT])
|
attrs[ATTR_CURRENT] = self.dps(self._config[CONF_CURRENT])
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
black==20.8b1
|
black==20.8b1
|
||||||
mypy==0.782
|
mypy==0.782
|
||||||
|
pydocstyle==5.1.1
|
2
tox.ini
2
tox.ini
@@ -30,7 +30,7 @@ commands =
|
|||||||
#codespell -q 4 -L {[tox]cs_exclude_words} --skip="*.pyc,*.pyi,*~" custom_components
|
#codespell -q 4 -L {[tox]cs_exclude_words} --skip="*.pyc,*.pyi,*~" custom_components
|
||||||
#flake8 cu
|
#flake8 cu
|
||||||
black --fast --check .
|
black --fast --check .
|
||||||
#pydocstyle -v custom_components
|
pydocstyle -v custom_components
|
||||||
|
|
||||||
[testenv:typing]
|
[testenv:typing]
|
||||||
whitelist_externals =
|
whitelist_externals =
|
||||||
|
Reference in New Issue
Block a user