Fix flake8 issues and enable flake8 in tox
This commit is contained in:
@@ -25,6 +25,7 @@ from .const import CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN
|
|||||||
|
|
||||||
|
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
pp = pprint.PrettyPrinter(indent=4)
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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}")
|
raise Exception(f"missing entity config for id {dps_id}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TuyaDevice:
|
class TuyaDevice:
|
||||||
"""Cache wrapper for pytuya.TuyaInterface."""
|
"""Cache wrapper for pytuya.TuyaInterface."""
|
||||||
|
|
||||||
@@ -160,7 +160,6 @@ class TuyaDevice:
|
|||||||
for i in range(5):
|
for i in range(5):
|
||||||
try:
|
try:
|
||||||
status = self._interface.status()
|
status = self._interface.status()
|
||||||
# print("STATUS OF [{}] IS [{}]".format(self._interface.address,status))
|
|
||||||
return status
|
return status
|
||||||
except Exception:
|
except Exception:
|
||||||
print(
|
print(
|
||||||
@@ -177,17 +176,18 @@ class TuyaDevice:
|
|||||||
raise ConnectionError("Failed to update status .")
|
raise ConnectionError("Failed to update status .")
|
||||||
|
|
||||||
def set_dps(self, state, dps_index):
|
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."""
|
"""Change the Tuya switch status and clear the cache."""
|
||||||
self._cached_status = ""
|
self._cached_status = ""
|
||||||
self._cached_status_time = 0
|
self._cached_status_time = 0
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
try:
|
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)
|
return self._interface.set_dps(state, dps_index)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(
|
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:
|
if i + 1 == 3:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
"""Constants for localtuya integration."""
|
"""Constants for localtuya integration."""
|
||||||
|
|
||||||
ATTR_CURRENT = 'current'
|
ATTR_CURRENT = "current"
|
||||||
ATTR_CURRENT_CONSUMPTION = 'current_consumption'
|
ATTR_CURRENT_CONSUMPTION = "current_consumption"
|
||||||
ATTR_VOLTAGE = 'voltage'
|
ATTR_VOLTAGE = "voltage"
|
||||||
|
|
||||||
CONF_LOCAL_KEY = "local_key"
|
CONF_LOCAL_KEY = "local_key"
|
||||||
CONF_PROTOCOL_VERSION = "protocol_version"
|
CONF_PROTOCOL_VERSION = "protocol_version"
|
||||||
@@ -15,9 +15,9 @@ CONF_CURRENT_CONSUMPTION = "current_consumption"
|
|||||||
CONF_VOLTAGE = "voltage"
|
CONF_VOLTAGE = "voltage"
|
||||||
|
|
||||||
# cover
|
# cover
|
||||||
CONF_OPEN_CMD = 'open_cmd'
|
CONF_OPEN_CMD = "open_cmd"
|
||||||
CONF_CLOSE_CMD = 'close_cmd'
|
CONF_CLOSE_CMD = "close_cmd"
|
||||||
CONF_STOP_CMD = 'stop_cmd'
|
CONF_STOP_CMD = "stop_cmd"
|
||||||
|
|
||||||
# sensor
|
# sensor
|
||||||
CONF_SCALING = "scaling"
|
CONF_SCALING = "scaling"
|
||||||
|
@@ -19,7 +19,7 @@ cover:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from time import time, sleep
|
from time import sleep
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@@ -50,7 +50,6 @@ from .const import (
|
|||||||
CONF_CLOSE_CMD,
|
CONF_CLOSE_CMD,
|
||||||
CONF_STOP_CMD,
|
CONF_STOP_CMD,
|
||||||
)
|
)
|
||||||
from .const import CONF_OPEN_CMD, CONF_CLOSE_CMD, CONF_STOP_CMD
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -79,9 +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):
|
||||||
"""Set up 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
|
|
||||||
)
|
|
||||||
if not entities_to_setup:
|
if not entities_to_setup:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -97,6 +94,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
|
|
||||||
async_add_entities(covers, True)
|
async_add_entities(covers, True)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up of the Tuya cover."""
|
"""Set up of the Tuya cover."""
|
||||||
return import_from_yaml(hass, config, DOMAIN)
|
return import_from_yaml(hass, config, DOMAIN)
|
||||||
@@ -113,12 +111,11 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize a new LocaltuyaCover."""
|
"""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)
|
super().__init__(device, config_entry, switchid, **kwargs)
|
||||||
self._state = None
|
self._state = None
|
||||||
self._position = 50
|
self._position = 50
|
||||||
print(
|
print(
|
||||||
"Initialized tuya cover [{}] with switch status [{}] and state [{}]".format(
|
"Initialized cover [{}] with status [{}] and state [{}]".format(
|
||||||
self.name, self._status, self._state
|
self.name, self._status, self._state
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -198,7 +195,6 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
|
|||||||
self.stop_cover()
|
self.stop_cover()
|
||||||
self._position = 50 # newpos
|
self._position = 50 # newpos
|
||||||
|
|
||||||
|
|
||||||
def open_cover(self, **kwargs):
|
def open_cover(self, **kwargs):
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
_LOGGER.debug("Launching command %s to cover ", self._config[CONF_OPEN_CMD])
|
_LOGGER.debug("Launching command %s to cover ", self._config[CONF_OPEN_CMD])
|
||||||
|
@@ -18,8 +18,9 @@ UDP_KEY = md5(b"yGAdlopoPVldABfn").digest()
|
|||||||
|
|
||||||
def decrypt_udp(message):
|
def decrypt_udp(message):
|
||||||
"""Decrypt encrypted UDP broadcasts."""
|
"""Decrypt encrypted UDP broadcasts."""
|
||||||
|
|
||||||
def _unpad(data):
|
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()
|
return _unpad(AES.new(UDP_KEY, AES.MODE_ECB).decrypt(message)).decode()
|
||||||
|
|
||||||
|
@@ -15,7 +15,6 @@ fan:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from time import time, sleep
|
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
FanEntity,
|
FanEntity,
|
||||||
@@ -59,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
for device_config in entities_to_setup:
|
for device_config in entities_to_setup:
|
||||||
fans.append(
|
fans.append(
|
||||||
LocaltuyaFan(
|
LocaltuyaFan(
|
||||||
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
||||||
config_entry,
|
config_entry,
|
||||||
device_config[CONF_ID],
|
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)
|
return import_from_yaml(hass, config, DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LocaltuyaFan(LocalTuyaEntity, FanEntity):
|
class LocaltuyaFan(LocalTuyaEntity, FanEntity):
|
||||||
"""Representation of a Tuya fan."""
|
"""Representation of a Tuya fan."""
|
||||||
|
|
||||||
|
@@ -11,9 +11,7 @@ light:
|
|||||||
friendly_name: This Light
|
friendly_name: This Light
|
||||||
protocol_version: 3.3
|
protocol_version: 3.3
|
||||||
"""
|
"""
|
||||||
import socket
|
|
||||||
import logging
|
import logging
|
||||||
from time import time, sleep
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
@@ -71,7 +69,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
|
|
||||||
lights.append(
|
lights.append(
|
||||||
LocaltuyaLight(
|
LocaltuyaLight(
|
||||||
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
||||||
config_entry,
|
config_entry,
|
||||||
device_config[CONF_ID],
|
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)
|
return import_from_yaml(hass, config, DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
||||||
"""Representation of a Tuya light."""
|
"""Representation of a Tuya light."""
|
||||||
|
|
||||||
|
@@ -17,18 +17,19 @@ Classes
|
|||||||
address (str): Device Network IP Address e.g. 10.0.1.99
|
address (str): Device Network IP Address e.g. 10.0.1.99
|
||||||
local_key (str, optional): The encryption key. Defaults to None.
|
local_key (str, optional): The encryption key. Defaults to None.
|
||||||
|
|
||||||
Functions
|
Functions
|
||||||
json = status() # returns json payload
|
json = status() # returns json payload
|
||||||
set_version(version) # 3.1 [default] or 3.3
|
set_version(version) # 3.1 [default] or 3.3
|
||||||
detect_available_dps() # returns a list of available dps provided by the device
|
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_dps(on, dps_index) # Set value of any dps index.
|
||||||
set_timer(num_secs):
|
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
|
||||||
@@ -37,13 +38,11 @@ Credits
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from itertools import chain
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import colorsys
|
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -160,20 +159,26 @@ def hex2bin(x):
|
|||||||
return bytes.fromhex(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_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 = {
|
payload_dict = {
|
||||||
"type_0a": {
|
"type_0a": {
|
||||||
"status": {"hexByte": "0a", "command": {"gwId": "", "devId": ""}},
|
"status": {"hexByte": "0a", "command": {"gwId": "", "devId": ""}},
|
||||||
"set": {"hexByte": "07", "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",
|
"suffix": "000000000000aa55",
|
||||||
},
|
},
|
||||||
"type_0d": {
|
"type_0d": {
|
||||||
"status": {"hexByte": "0d", "command": {"devId": "", "uid": "", "t": ""}},
|
"status": {"hexByte": "0d", "command": {"devId": "", "uid": "", "t": ""}},
|
||||||
"set": {"hexByte": "07", "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",
|
"suffix": "000000000000aa55",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -235,7 +240,8 @@ class TuyaInterface:
|
|||||||
try:
|
try:
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
# print("FIRST: Received %d bytes" % len(data) )
|
# 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:
|
if len(data) < 40:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
@@ -250,19 +256,21 @@ class TuyaInterface:
|
|||||||
|
|
||||||
def detect_available_dps(self):
|
def detect_available_dps(self):
|
||||||
"""Return which datapoints are supported by the device."""
|
"""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
|
||||||
# experience shows that the dps available are usually in the ranges [1-25] and [100-110]
|
# list of available dps experience shows that the dps available are usually
|
||||||
# need to split the bruteforcing in different steps due to request payload limitation (max. length = 255)
|
# 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 = {}
|
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.dps_to_request = {"1": None}
|
||||||
self.add_dps_to_request(range(2, 11))
|
self.add_dps_to_request(range(2, 11))
|
||||||
try:
|
try:
|
||||||
data = self.status()
|
data = self.status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to get status: [{}]".format(e))
|
print("Failed to get status: [{}]".format(e))
|
||||||
raise CannotConnect
|
raise
|
||||||
detected_dps.update(data["dps"])
|
detected_dps.update(data["dps"])
|
||||||
|
|
||||||
if self.dev_type == "type_0a":
|
if self.dev_type == "type_0a":
|
||||||
@@ -274,7 +282,7 @@ class TuyaInterface:
|
|||||||
data = self.status()
|
data = self.status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to get status: [{}]".format(e))
|
print("Failed to get status: [{}]".format(e))
|
||||||
raise CannotConnect
|
raise
|
||||||
detected_dps.update(data["dps"])
|
detected_dps.update(data["dps"])
|
||||||
|
|
||||||
self.dps_to_request = {"1": None}
|
self.dps_to_request = {"1": None}
|
||||||
@@ -283,7 +291,7 @@ class TuyaInterface:
|
|||||||
data = self.status()
|
data = self.status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to get status: [{}]".format(e))
|
print("Failed to get status: [{}]".format(e))
|
||||||
raise CannotConnect
|
raise
|
||||||
detected_dps.update(data["dps"])
|
detected_dps.update(data["dps"])
|
||||||
|
|
||||||
self.dps_to_request = {"1": None}
|
self.dps_to_request = {"1": None}
|
||||||
@@ -292,7 +300,7 @@ class TuyaInterface:
|
|||||||
data = self.status()
|
data = self.status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to get status: [{}]".format(e))
|
print("Failed to get status: [{}]".format(e))
|
||||||
raise CannotConnect
|
raise
|
||||||
detected_dps.update(data["dps"])
|
detected_dps.update(data["dps"])
|
||||||
# print("DATA IS [{}] detected_dps [{}]".format(data,detected_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
|
# calc the CRC of everything except where the CRC goes and the suffix
|
||||||
hex_crc = format(binascii.crc32(buffer[:-8]) & 0xFFFFFFFF, "08X")
|
hex_crc = format(binascii.crc32(buffer[:-8]) & 0xFFFFFFFF, "08X")
|
||||||
buffer = buffer[:-8] + hex2bin(hex_crc) + buffer[-4:]
|
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
|
return buffer
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
@@ -418,7 +424,8 @@ class TuyaInterface:
|
|||||||
result = result[15:]
|
result = result[15:]
|
||||||
|
|
||||||
log.debug("result=%r", result)
|
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)
|
# print('result %r' % result)
|
||||||
if result.startswith(b"{"):
|
if result.startswith(b"{"):
|
||||||
# this is the regular expected code path
|
# this is the regular expected code path
|
||||||
@@ -427,12 +434,13 @@ class TuyaInterface:
|
|||||||
result = json.loads(result)
|
result = json.loads(result)
|
||||||
elif result.startswith(PROTOCOL_VERSION_BYTES_31):
|
elif result.startswith(PROTOCOL_VERSION_BYTES_31):
|
||||||
# got an encrypted payload, happens occasionally
|
# 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
|
# NOTE dps.2 may or may not be present
|
||||||
result = result[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
|
result = result[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
|
||||||
result = result[
|
# remove (what I'm guessing, but not confirmed is) 16-bytes of MD5
|
||||||
16:
|
# 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)
|
cipher = AESCipher(self.local_key)
|
||||||
result = cipher.decrypt(result)
|
result = cipher.decrypt(result)
|
||||||
print("decrypted result=[{}]".format(result))
|
print("decrypted result=[{}]".format(result))
|
||||||
@@ -487,7 +495,8 @@ class TuyaInterface:
|
|||||||
"""
|
"""
|
||||||
# FIXME / TODO support schemas? Accept timer id number as parameter?
|
# 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()
|
status = self.status()
|
||||||
devices = status["dps"]
|
devices = status["dps"]
|
||||||
devices_numbers = list(devices.keys())
|
devices_numbers = list(devices.keys())
|
||||||
|
@@ -15,7 +15,6 @@ sensor:
|
|||||||
device_class: current
|
device_class: current
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from time import time, sleep
|
|
||||||
|
|
||||||
import voluptuous as vol
|
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:
|
for device_config in entities_to_setup:
|
||||||
sensors.append(
|
sensors.append(
|
||||||
LocaltuyaSensor(
|
LocaltuyaSensor(
|
||||||
TuyaCache(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
TuyaDevice(tuyainterface, config_entry.data[CONF_FRIENDLY_NAME]),
|
||||||
config_entry,
|
config_entry,
|
||||||
device_config[CONF_ID],
|
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)
|
return import_from_yaml(hass, config, DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LocaltuyaSensor(LocalTuyaEntity):
|
class LocaltuyaSensor(LocalTuyaEntity):
|
||||||
"""Representation of a Tuya sensor."""
|
"""Representation of a Tuya sensor."""
|
||||||
|
|
||||||
|
@@ -25,7 +25,6 @@ switch:
|
|||||||
id: 7
|
id: 7
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from time import time, sleep
|
|
||||||
|
|
||||||
import voluptuous as vol
|
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":
|
if device_config.get(CONF_CURRENT, "-1") != "-1":
|
||||||
tuyainterface.add_dps_to_request(device_config.get(CONF_CURRENT))
|
tuyainterface.add_dps_to_request(device_config.get(CONF_CURRENT))
|
||||||
if device_config.get(CONF_CURRENT_CONSUMPTION, "-1") != "-1":
|
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":
|
if device_config.get(CONF_VOLTAGE, "-1") != "-1":
|
||||||
tuyainterface.add_dps_to_request(device_config.get(CONF_VOLTAGE))
|
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)
|
return import_from_yaml(hass, config, DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
|
class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
|
||||||
"""Representation of a Tuya switch."""
|
"""Representation of a Tuya switch."""
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
|
|||||||
super().__init__(device, config_entry, switchid, **kwargs)
|
super().__init__(device, config_entry, switchid, **kwargs)
|
||||||
self._state = None
|
self._state = None
|
||||||
print(
|
print(
|
||||||
"Initialized tuya switch [{}] with switch status [{}] and state [{}]".format(
|
"Initialized switch [{}] with status [{}] and state [{}]".format(
|
||||||
self.name, self._status, self._state
|
self.name, self._status, self._state
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = ["py37", "py38"]
|
target-version = ["py37", "py38"]
|
||||||
include = "custom_components/localtuya/*.py"
|
include = 'custom_components/localtuya/.*\.py'
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
black==20.8b1
|
black==20.8b1
|
||||||
|
flake8==3.8.3
|
||||||
mypy==0.782
|
mypy==0.782
|
||||||
pydocstyle==5.1.1
|
pydocstyle==5.1.1
|
4
setup.cfg
Normal file
4
setup.cfg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[flake8]
|
||||||
|
exclude = .git,.tox
|
||||||
|
max-line-length = 88
|
||||||
|
ignore = E203, W503
|
2
tox.ini
2
tox.ini
@@ -28,7 +28,7 @@ deps =
|
|||||||
{[testenv]deps}
|
{[testenv]deps}
|
||||||
commands =
|
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 custom_components
|
||||||
black --fast --check .
|
black --fast --check .
|
||||||
pydocstyle -v custom_components
|
pydocstyle -v custom_components
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user