Fix flake8 issues and enable flake8 in tox

This commit is contained in:
Pierre Ståhl
2020-09-23 09:31:37 +02:00
parent a18c69f8eb
commit 4e72767b30
13 changed files with 67 additions and 64 deletions

View File

@@ -25,6 +25,7 @@ from .const import CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN
import pprint
pp = pprint.PrettyPrinter(indent=4)
_LOGGER = logging.getLogger(__name__)
@@ -138,7 +139,6 @@ def get_entity_config(config_entry, dps_id):
raise Exception(f"missing entity config for id {dps_id}")
class TuyaDevice:
"""Cache wrapper for pytuya.TuyaInterface."""
@@ -160,7 +160,6 @@ class TuyaDevice:
for i in range(5):
try:
status = self._interface.status()
# print("STATUS OF [{}] IS [{}]".format(self._interface.address,status))
return status
except Exception:
print(
@@ -177,17 +176,18 @@ class TuyaDevice:
raise ConnectionError("Failed to update status .")
def set_dps(self, state, dps_index):
#_LOGGER.info("running def set_dps from cover")
# _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 dps_index=%s", state, dps_index)
return self._interface.set_dps(state, dps_index)
except Exception:
print(
"Failed to set status of device [{}]".format(self._interface.address)
"Failed to set status of device [{}]".format(
self._interface.address
)
)
if i + 1 == 3:
_LOGGER.error(

View File

@@ -1,8 +1,8 @@
"""Constants for localtuya integration."""
ATTR_CURRENT = 'current'
ATTR_CURRENT_CONSUMPTION = 'current_consumption'
ATTR_VOLTAGE = 'voltage'
ATTR_CURRENT = "current"
ATTR_CURRENT_CONSUMPTION = "current_consumption"
ATTR_VOLTAGE = "voltage"
CONF_LOCAL_KEY = "local_key"
CONF_PROTOCOL_VERSION = "protocol_version"
@@ -15,9 +15,9 @@ CONF_CURRENT_CONSUMPTION = "current_consumption"
CONF_VOLTAGE = "voltage"
# cover
CONF_OPEN_CMD = 'open_cmd'
CONF_CLOSE_CMD = 'close_cmd'
CONF_STOP_CMD = 'stop_cmd'
CONF_OPEN_CMD = "open_cmd"
CONF_CLOSE_CMD = "close_cmd"
CONF_STOP_CMD = "stop_cmd"
# sensor
CONF_SCALING = "scaling"

View File

@@ -19,7 +19,7 @@ cover:
"""
import logging
from time import time, sleep
from time import sleep
import voluptuous as vol
@@ -50,7 +50,6 @@ from .const import (
CONF_CLOSE_CMD,
CONF_STOP_CMD,
)
from .const import CONF_OPEN_CMD, CONF_CLOSE_CMD, CONF_STOP_CMD
_LOGGER = logging.getLogger(__name__)
@@ -79,9 +78,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up a Tuya cover 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:
return
@@ -97,6 +94,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async_add_entities(covers, True)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up of the Tuya cover."""
return import_from_yaml(hass, config, DOMAIN)
@@ -113,12 +111,11 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
**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)
super().__init__(device, config_entry, switchid, **kwargs)
self._state = None
self._position = 50
print(
"Initialized tuya cover [{}] with switch status [{}] and state [{}]".format(
"Initialized cover [{}] with status [{}] and state [{}]".format(
self.name, self._status, self._state
)
)
@@ -198,7 +195,6 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
self.stop_cover()
self._position = 50 # newpos
def open_cover(self, **kwargs):
"""Open the cover."""
_LOGGER.debug("Launching command %s to cover ", self._config[CONF_OPEN_CMD])

View File

@@ -18,8 +18,9 @@ UDP_KEY = md5(b"yGAdlopoPVldABfn").digest()
def decrypt_udp(message):
"""Decrypt encrypted UDP broadcasts."""
def _unpad(data):
return data[:-ord(data[len(data) - 1:])]
return data[: -ord(data[len(data) - 1 :])]
return _unpad(AES.new(UDP_KEY, AES.MODE_ECB).decrypt(message)).decode()

View File

@@ -15,7 +15,6 @@ fan:
"""
import logging
from time import time, sleep
from homeassistant.components.fan import (
FanEntity,
@@ -59,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for device_config in entities_to_setup:
fans.append(
LocaltuyaFan(
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
config_entry,
device_config[CONF_ID],
)
@@ -73,7 +72,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return import_from_yaml(hass, config, DOMAIN)
class LocaltuyaFan(LocalTuyaEntity, FanEntity):
"""Representation of a Tuya fan."""

View File

@@ -11,9 +11,7 @@ light:
friendly_name: This Light
protocol_version: 3.3
"""
import socket
import logging
from time import time, sleep
from homeassistant.const import (
CONF_ID,
@@ -71,7 +69,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
lights.append(
LocaltuyaLight(
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
config_entry,
device_config[CONF_ID],
)
@@ -85,7 +83,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return import_from_yaml(hass, config, DOMAIN)
class LocaltuyaLight(LocalTuyaEntity, LightEntity):
"""Representation of a Tuya light."""

View File

@@ -21,7 +21,8 @@ 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)
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):
@@ -37,13 +38,11 @@ Credits
import base64
from hashlib import md5
from itertools import chain
import json
import logging
import socket
import sys
import time
import colorsys
import binascii
try:
@@ -160,20 +159,26 @@ def hex2bin(x):
return bytes.fromhex(x)
# This is intended to match requests.json payload at https://github.com/codetheweb/tuyapi :
# This is intended to match requests.json payload at
# https://github.com/codetheweb/tuyapi :
# type_0a devices require the 0a command as the status request
# type_0d devices require the 0d command as the status request, and the list of dps used set to null in the request payload (see generate_payload method)
# type_0d devices require the 0d command as the status request, and the list of
# dps used set to null in the request payload (see generate_payload method)
# prefix: # Next byte is command byte ("hexByte") some zero padding, then length
# of remaining payload, i.e. command + suffix (unclear if multiple bytes used for
# length, zero padding implies could be more than one byte)
payload_dict = {
"type_0a": {
"status": {"hexByte": "0a", "command": {"gwId": "", "devId": ""}},
"set": {"hexByte": "07", "command": {"devId": "", "uid": "", "t": ""}},
"prefix": "000055aa00000000000000", # Next byte is command byte ("hexByte") some zero padding, then length of remaining payload, i.e. command + suffix (unclear if multiple bytes used for length, zero padding implies could be more than one byte)
"prefix": "000055aa00000000000000",
"suffix": "000000000000aa55",
},
"type_0d": {
"status": {"hexByte": "0d", "command": {"devId": "", "uid": "", "t": ""}},
"set": {"hexByte": "07", "command": {"devId": "", "uid": "", "t": ""}},
"prefix": "000055aa00000000000000", # Next byte is command byte ("hexByte") some zero padding, then length of remaining payload, i.e. command + suffix (unclear if multiple bytes used for length, zero padding implies could be more than one byte)
"prefix": "000055aa00000000000000",
"suffix": "000000000000aa55",
},
}
@@ -235,7 +240,8 @@ class TuyaInterface:
try:
data = s.recv(1024)
# print("FIRST: Received %d bytes" % len(data) )
# sometimes the first packet does not contain data (typically 28 bytes): need to read again
# sometimes the first packet does not contain data (typically 28 bytes):
# need to read again
if len(data) < 40:
time.sleep(0.1)
data = s.recv(1024)
@@ -250,19 +256,21 @@ class TuyaInterface:
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
# 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)
# 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] need to split the bruteforcing in
# different steps due to request payload limitation (max. length = 255)
detected_dps = {}
# dps 1 must always be sent, otherwise it might fail in case no dps is found in the requested range
# dps 1 must always be sent, otherwise it might fail in case no dps is found
# in the requested range
self.dps_to_request = {"1": None}
self.add_dps_to_request(range(2, 11))
try:
data = self.status()
except Exception as e:
print("Failed to get status: [{}]".format(e))
raise CannotConnect
raise
detected_dps.update(data["dps"])
if self.dev_type == "type_0a":
@@ -274,7 +282,7 @@ class TuyaInterface:
data = self.status()
except Exception as e:
print("Failed to get status: [{}]".format(e))
raise CannotConnect
raise
detected_dps.update(data["dps"])
self.dps_to_request = {"1": None}
@@ -283,7 +291,7 @@ class TuyaInterface:
data = self.status()
except Exception as e:
print("Failed to get status: [{}]".format(e))
raise CannotConnect
raise
detected_dps.update(data["dps"])
self.dps_to_request = {"1": None}
@@ -292,7 +300,7 @@ class TuyaInterface:
data = self.status()
except Exception as e:
print("Failed to get status: [{}]".format(e))
raise CannotConnect
raise
detected_dps.update(data["dps"])
# print("DATA IS [{}] detected_dps [{}]".format(data,detected_dps))
@@ -400,8 +408,6 @@ class TuyaInterface:
# calc the CRC of everything except where the CRC goes and the suffix
hex_crc = format(binascii.crc32(buffer[:-8]) & 0xFFFFFFFF, "08X")
buffer = buffer[:-8] + hex2bin(hex_crc) + buffer[-4:]
# print('full buffer(%d) %r' % (len(buffer), bin2hex(buffer, pretty=True) ))
# print('full buffer(%d) %r' % (len(buffer), " ".join("{:02x}".format(ord(c)) for c in buffer)))
return buffer
def status(self):
@@ -418,7 +424,8 @@ class TuyaInterface:
result = result[15:]
log.debug("result=%r", result)
# result = data[data.find('{'):data.rfind('}')+1] # naive marker search, hope neither { nor } occur in header/footer
# result = data[data.find('{'):data.rfind('}')+1] # naive marker search,
# hope neither { nor } occur in header/footer
# print('result %r' % result)
if result.startswith(b"{"):
# this is the regular expected code path
@@ -427,12 +434,13 @@ class TuyaInterface:
result = json.loads(result)
elif result.startswith(PROTOCOL_VERSION_BYTES_31):
# got an encrypted payload, happens occasionally
# expect resulting json to look similar to:: {"devId":"ID","dps":{"1":true,"2":0},"t":EPOCH_SECS,"s":3_DIGIT_NUM}
# expect resulting json to look similar to:
# {"devId":"ID","dps":{"1":true,"2":0},"t":EPOCH_SECS,"s":3_DIGIT_NUM}
# NOTE dps.2 may or may not be present
result = result[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
result = result[
16:
] # remove (what I'm guessing, but not confirmed is) 16-bytes of MD5 hexdigest of payload
# remove (what I'm guessing, but not confirmed is) 16-bytes of MD5
# hexdigest of payload
result = result[16:]
cipher = AESCipher(self.local_key)
result = cipher.decrypt(result)
print("decrypted result=[{}]".format(result))
@@ -487,7 +495,8 @@ class TuyaInterface:
"""
# FIXME / TODO support schemas? Accept timer id number as parameter?
# Dumb heuristic; Query status, pick last device id as that is probably the timer
# Dumb heuristic; Query status, pick last device id as that is probably
# the timer
status = self.status()
devices = status["dps"]
devices_numbers = list(devices.keys())

View File

@@ -15,7 +15,6 @@ sensor:
device_class: current
"""
import logging
from time import time, sleep
import voluptuous as vol
@@ -65,7 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for device_config in entities_to_setup:
sensors.append(
LocaltuyaSensor(
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
config_entry,
device_config[CONF_ID],
)
@@ -79,7 +78,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return import_from_yaml(hass, config, DOMAIN)
class LocaltuyaSensor(LocalTuyaEntity):
"""Representation of a Tuya sensor."""

View File

@@ -25,7 +25,6 @@ switch:
id: 7
"""
import logging
from time import time, sleep
import voluptuous as vol
@@ -104,7 +103,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if device_config.get(CONF_CURRENT, "-1") != "-1":
tuyainterface.add_dps_to_request(device_config.get(CONF_CURRENT))
if device_config.get(CONF_CURRENT_CONSUMPTION, "-1") != "-1":
tuyainterface.add_dps_to_request(device_config.get(CONF_CURRENT_CONSUMPTION))
tuyainterface.add_dps_to_request(
device_config.get(CONF_CURRENT_CONSUMPTION)
)
if device_config.get(CONF_VOLTAGE, "-1") != "-1":
tuyainterface.add_dps_to_request(device_config.get(CONF_VOLTAGE))
@@ -124,8 +125,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return import_from_yaml(hass, config, DOMAIN)
class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
"""Representation of a Tuya switch."""
@@ -140,7 +139,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
super().__init__(device, config_entry, switchid, **kwargs)
self._state = None
print(
"Initialized tuya switch [{}] with switch status [{}] and state [{}]".format(
"Initialized switch [{}] with status [{}] and state [{}]".format(
self.name, self._status, self._state
)
)

View File

@@ -1,3 +1,3 @@
[tool.black]
target-version = ["py37", "py38"]
include = "custom_components/localtuya/*.py"
include = 'custom_components/localtuya/.*\.py'

View File

@@ -1,3 +1,4 @@
black==20.8b1
flake8==3.8.3
mypy==0.782
pydocstyle==5.1.1

4
setup.cfg Normal file
View File

@@ -0,0 +1,4 @@
[flake8]
exclude = .git,.tox
max-line-length = 88
ignore = E203, W503

View File

@@ -28,7 +28,7 @@ deps =
{[testenv]deps}
commands =
#codespell -q 4 -L {[tox]cs_exclude_words} --skip="*.pyc,*.pyi,*~" custom_components
#flake8 cu
flake8 custom_components
black --fast --check .
pydocstyle -v custom_components