code clean up (#243)

This commit is contained in:
Daniel Høyer Iversen 2019-05-19 17:54:14 +02:00 committed by GitHub
parent c9a1c106a7
commit a75f98720e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,6 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
from datetime import datetime from datetime import datetime
try: try:
from Crypto.Cipher import AES from Crypto.Cipher import AES
except ImportError as e: except ImportError as e:
@ -9,7 +10,6 @@ except ImportError as e:
import time import time
import random import random
import socket import socket
import sys
import threading import threading
import codecs import codecs
@ -60,6 +60,7 @@ def gendevice(devtype, host, mac):
return device(host=host, mac=mac, devtype=devtype) return device(host=host, mac=mac, devtype=devtype)
return deviceClass(host=host, mac=mac, devtype=devtype) return deviceClass(host=host, mac=mac, devtype=devtype)
def discover(timeout=None, local_ip_address=None): def discover(timeout=None, local_ip_address=None):
if local_ip_address is None: if local_ip_address is None:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -69,13 +70,13 @@ def discover(timeout=None, local_ip_address=None):
cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
cs.bind((local_ip_address,0)) cs.bind((local_ip_address, 0))
port = cs.getsockname()[1] port = cs.getsockname()[1]
starttime = time.time() starttime = time.time()
devices = [] devices = []
timezone = int(time.timezone/-3600) timezone = int(time.timezone / -3600)
packet = bytearray(0x30) packet = bytearray(0x30)
year = datetime.now().year year = datetime.now().year
@ -122,9 +123,8 @@ def discover(timeout=None, local_ip_address=None):
mac = responsepacket[0x3a:0x40] mac = responsepacket[0x3a:0x40]
devtype = responsepacket[0x34] | responsepacket[0x35] << 8 devtype = responsepacket[0x34] | responsepacket[0x35] << 8
return gendevice(devtype, host, mac) return gendevice(devtype, host, mac)
else:
while (time.time() - starttime) < timeout: while (time.time() - starttime) < timeout:
cs.settimeout(timeout - (time.time() - starttime)) cs.settimeout(timeout - (time.time() - starttime))
try: try:
@ -140,7 +140,6 @@ def discover(timeout=None, local_ip_address=None):
return devices return devices
class device: class device:
def __init__(self, host, mac, devtype, timeout=10): def __init__(self, host, mac, devtype, timeout=10):
self.host = host self.host = host
@ -148,13 +147,15 @@ class device:
self.devtype = devtype self.devtype = devtype
self.timeout = timeout self.timeout = timeout
self.count = random.randrange(0xffff) self.count = random.randrange(0xffff)
self.key = bytearray([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]) self.key = bytearray(
self.iv = bytearray([0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]) [0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02])
self.iv = bytearray(
[0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58])
self.id = bytearray([0, 0, 0, 0]) self.id = bytearray([0, 0, 0, 0])
self.cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.cs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.cs.bind(('',0)) self.cs.bind(('', 0))
self.type = "Unknown" self.type = "Unknown"
self.lock = threading.Lock() self.lock = threading.Lock()
@ -166,12 +167,12 @@ class device:
self.decrypt = self.decrypt_pycrypto self.decrypt = self.decrypt_pycrypto
def encrypt_pyaes(self, payload): def encrypt_pyaes(self, payload):
aes = pyaes.AESModeOfOperationCBC(self.key, iv = bytes(self.iv)) aes = pyaes.AESModeOfOperationCBC(self.key, iv=bytes(self.iv))
return b"".join([aes.encrypt(bytes(payload[i:i+16])) for i in range(0, len(payload), 16)]) return b"".join([aes.encrypt(bytes(payload[i:i + 16])) for i in range(0, len(payload), 16)])
def decrypt_pyaes(self, payload): def decrypt_pyaes(self, payload):
aes = pyaes.AESModeOfOperationCBC(self.key, iv = bytes(self.iv)) aes = pyaes.AESModeOfOperationCBC(self.key, iv=bytes(self.iv))
return b"".join([aes.decrypt(bytes(payload[i:i+16])) for i in range(0, len(payload), 16)]) return b"".join([aes.decrypt(bytes(payload[i:i + 16])) for i in range(0, len(payload), 16)])
def encrypt_pycrypto(self, payload): def encrypt_pycrypto(self, payload):
aes = AES.new(bytes(self.key), AES.MODE_CBC, bytes(self.iv)) aes = AES.new(bytes(self.key), AES.MODE_CBC, bytes(self.iv))
@ -255,9 +256,9 @@ class device:
packet[0x33] = self.id[3] packet[0x33] = self.id[3]
# pad the payload for AES encryption # pad the payload for AES encryption
if len(payload)>0: if payload:
numpad=(len(payload)//16+1)*16 numpad = (len(payload) // 16 + 1) * 16
payload=payload.ljust(numpad, b"\x00") payload = payload.ljust(numpad, b"\x00")
checksum = 0xbeaf checksum = 0xbeaf
for i in range(len(payload)): for i in range(len(payload)):
@ -294,7 +295,7 @@ class device:
class mp1(device): class mp1(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "MP1" self.type = "MP1"
@ -307,16 +308,14 @@ class mp1(device):
packet[0x03] = 0xa5 packet[0x03] = 0xa5
packet[0x04] = 0x5a packet[0x04] = 0x5a
packet[0x05] = 0x5a packet[0x05] = 0x5a
packet[0x06] = 0xb2 + ((sid_mask<<1) if state else sid_mask) packet[0x06] = 0xb2 + ((sid_mask << 1) if state else sid_mask)
packet[0x07] = 0xc0 packet[0x07] = 0xc0
packet[0x08] = 0x02 packet[0x08] = 0x02
packet[0x0a] = 0x03 packet[0x0a] = 0x03
packet[0x0d] = sid_mask packet[0x0d] = sid_mask
packet[0x0e] = sid_mask if state else 0 packet[0x0e] = sid_mask if state else 0
response = self.send_packet(0x6a, packet) self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8)
def set_power(self, sid, state): def set_power(self, sid, state):
"""Sets the power state of the smart power strip.""" """Sets the power state of the smart power strip."""
@ -337,9 +336,10 @@ class mp1(device):
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
state = payload[0x0e] state = payload[0x0e]
else: else:
state = ord(payload[0x0e]) state = ord(payload[0x0e])
@ -357,7 +357,7 @@ class mp1(device):
class sp1(device): class sp1(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "SP1" self.type = "SP1"
@ -368,7 +368,7 @@ class sp1(device):
class sp2(device): class sp2(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "SP2" self.type = "SP2"
@ -398,19 +398,12 @@ class sp2(device):
packet[0] = 1 packet[0] = 1
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
if payload[0x4] == 1 or payload[0x4] == 3 or payload[0x4] == 0xFD: return bool(payload[0x4] == 1 or payload[0x4] == 3 or payload[0x4] == 0xFD)
state = True return bool(ord(payload[0x4]) == 1 or ord(payload[0x4]) == 3 or ord(payload[0x4]) == 0xFD)
else:
state = False
else:
if ord(payload[0x4]) == 1 or ord(payload[0x4]) == 3 or ord(payload[0x4]) == 0xFD:
state = True
else:
state = False
return state
def check_nightlight(self): def check_nightlight(self):
"""Returns the power state of the smart plug.""" """Returns the power state of the smart plug."""
@ -418,35 +411,30 @@ class sp2(device):
packet[0] = 1 packet[0] = 1
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
if payload[0x4] == 2 or payload[0x4] == 3 or payload[0x4] == 0xFF: return bool(payload[0x4] == 2 or payload[0x4] == 3 or payload[0x4] == 0xFF)
state = True return bool(ord(payload[0x4]) == 2 or ord(payload[0x4]) == 3 or ord(payload[0x4]) == 0xFF)
else:
state = False
else:
if ord(payload[0x4]) == 2 or ord(payload[0x4]) == 3 or ord(payload[0x4]) == 0xFF:
state = True
else:
state = False
return state
def get_energy(self): def get_energy(self):
packet = bytearray([8, 0, 254, 1, 5, 1, 0, 0, 0, 45]) packet = bytearray([8, 0, 254, 1, 5, 1, 0, 0, 0, 45])
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x07]) == int: if isinstance(payload[0x7], int):
energy = int(hex(payload[0x07] * 256 + payload[0x06])[2:]) + int(hex(payload[0x05])[2:])/100.0 energy = int(hex(payload[0x07] * 256 + payload[0x06])[2:]) + int(hex(payload[0x05])[2:]) / 100.0
else: else:
energy = int(hex(ord(payload[0x07]) * 256 + ord(payload[0x06]))[2:]) + int(hex(ord(payload[0x05]))[2:])/100.0 energy = int(hex(ord(payload[0x07]) * 256 + ord(payload[0x06]))[2:]) + int(
hex(ord(payload[0x05]))[2:]) / 100.0
return energy return energy
class a1(device): class a1(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "A1" self.type = "A1"
@ -455,10 +443,11 @@ class a1(device):
packet[0] = 1 packet[0] = 1
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
data = {} data = {}
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
data['temperature'] = (payload[0x4] * 10 + payload[0x5]) / 10.0 data['temperature'] = (payload[0x4] * 10 + payload[0x5]) / 10.0
data['humidity'] = (payload[0x6] * 10 + payload[0x7]) / 10.0 data['humidity'] = (payload[0x6] * 10 + payload[0x7]) / 10.0
light = payload[0x8] light = payload[0x8]
@ -505,10 +494,11 @@ class a1(device):
packet[0] = 1 packet[0] = 1
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
data = {} data = {}
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
data['temperature'] = (payload[0x4] * 10 + payload[0x5]) / 10.0 data['temperature'] = (payload[0x4] * 10 + payload[0x5]) / 10.0
data['humidity'] = (payload[0x6] * 10 + payload[0x7]) / 10.0 data['humidity'] = (payload[0x6] * 10 + payload[0x7]) / 10.0
data['light'] = payload[0x8] data['light'] = payload[0x8]
@ -524,7 +514,7 @@ class a1(device):
class rm(device): class rm(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "RM2" self.type = "RM2"
@ -533,7 +523,8 @@ class rm(device):
packet[0] = 4 packet[0] = 4
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
return payload[0x04:] return payload[0x04:]
@ -562,7 +553,8 @@ class rm(device):
packet[0] = 0x1a packet[0] = 0x1a
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return False
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if payload[0x04] == 1: if payload[0x04] == 1:
return True return True
@ -573,7 +565,8 @@ class rm(device):
packet[0] = 0x1b packet[0] = 0x1b
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return False
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if payload[0x04] == 1: if payload[0x04] == 1:
return True return True
@ -584,9 +577,10 @@ class rm(device):
packet[0] = 1 packet[0] = 1
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return False
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
if type(payload[0x4]) == int: if isinstance(payload[0x4], int):
temp = (payload[0x4] * 10 + payload[0x5]) / 10.0 temp = (payload[0x4] * 10 + payload[0x5]) / 10.0
else: else:
temp = (ord(payload[0x4]) * 10 + ord(payload[0x5])) / 10.0 temp = (ord(payload[0x4]) * 10 + ord(payload[0x5])) / 10.0
@ -595,7 +589,7 @@ class rm(device):
# For legacy compatibility - don't use this # For legacy compatibility - don't use this
class rm2(rm): class rm2(rm):
def __init__ (self): def __init__(self):
device.__init__(self, None, None, None) device.__init__(self, None, None, None)
def discover(self): def discover(self):
@ -605,7 +599,7 @@ class rm2(rm):
class hysen(device): class hysen(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "Hysen heating controller" self.type = "Hysen heating controller"
@ -614,13 +608,13 @@ class hysen(device):
# Returns decrypted payload # Returns decrypted payload
# New behaviour: raises a ValueError if the device response indicates an error or CRC check fails # New behaviour: raises a ValueError if the device response indicates an error or CRC check fails
# The function prepends length (2 bytes) and appends CRC # The function prepends length (2 bytes) and appends CRC
def send_request(self,input_payload): def send_request(self, input_payload):
from PyCRC.CRC16 import CRC16 from PyCRC.CRC16 import CRC16
crc = CRC16(modbus_flag=True).calculate(bytes(input_payload)) crc = CRC16(modbus_flag=True).calculate(bytes(input_payload))
# first byte is length, +2 for CRC16 # first byte is length, +2 for CRC16
request_payload = bytearray([len(input_payload) + 2,0x00]) request_payload = bytearray([len(input_payload) + 2, 0x00])
request_payload.extend(input_payload) request_payload.extend(input_payload)
# append CRC # append CRC
@ -633,41 +627,40 @@ class hysen(device):
# check for error # check for error
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err: if err:
raise ValueError('broadlink_response_error',err) raise ValueError('broadlink_response_error', err)
response_payload = bytearray(self.decrypt(bytes(response[0x38:]))) response_payload = bytearray(self.decrypt(bytes(response[0x38:])))
# experimental check on CRC in response (first 2 bytes are len, and trailing bytes are crc) # experimental check on CRC in response (first 2 bytes are len, and trailing bytes are crc)
response_payload_len = response_payload[0] response_payload_len = response_payload[0]
if response_payload_len + 2 > len(response_payload): if response_payload_len + 2 > len(response_payload):
raise ValueError('hysen_response_error','first byte of response is not length') raise ValueError('hysen_response_error', 'first byte of response is not length')
crc = CRC16(modbus_flag=True).calculate(bytes(response_payload[2:response_payload_len])) crc = CRC16(modbus_flag=True).calculate(bytes(response_payload[2:response_payload_len]))
if (response_payload[response_payload_len] == crc & 0xFF) and (response_payload[response_payload_len+1] == (crc >> 8) & 0xFF): if (response_payload[response_payload_len] == crc & 0xFF) and (
response_payload[response_payload_len + 1] == (crc >> 8) & 0xFF):
return response_payload[2:response_payload_len] return response_payload[2:response_payload_len]
else: raise ValueError('hysen_response_error', 'CRC check on response failed')
raise ValueError('hysen_response_error','CRC check on response failed')
# Get current room temperature in degrees celsius # Get current room temperature in degrees celsius
def get_temp(self): def get_temp(self):
payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x08])) payload = self.send_request(bytearray([0x01, 0x03, 0x00, 0x00, 0x00, 0x08]))
return payload[0x05] / 2.0 return payload[0x05] / 2.0
# Get current external temperature in degrees celsius # Get current external temperature in degrees celsius
def get_external_temp(self): def get_external_temp(self):
payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x08])) payload = self.send_request(bytearray([0x01, 0x03, 0x00, 0x00, 0x00, 0x08]))
return payload[18] / 2.0 return payload[18] / 2.0
# Get full status (including timer schedule) # Get full status (including timer schedule)
def get_full_status(self): def get_full_status(self):
payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x16])) payload = self.send_request(bytearray([0x01, 0x03, 0x00, 0x00, 0x00, 0x16]))
data = {} data = {}
data['remote_lock'] = payload[3] & 1 data['remote_lock'] = payload[3] & 1
data['power'] = payload[4] & 1 data['power'] = payload[4] & 1
data['active'] = (payload[4] >> 4) & 1 data['active'] = (payload[4] >> 4) & 1
data['temp_manual'] = (payload[4] >> 6) & 1 data['temp_manual'] = (payload[4] >> 6) & 1
data['room_temp'] = (payload[5] & 255)/2.0 data['room_temp'] = (payload[5] & 255) / 2.0
data['thermostat_temp'] = (payload[6] & 255)/2.0 data['thermostat_temp'] = (payload[6] & 255) / 2.0
data['auto_mode'] = payload[7] & 15 data['auto_mode'] = payload[7] & 15
data['loop_mode'] = (payload[7] >> 4) & 15 data['loop_mode'] = (payload[7] >> 4) & 15
data['sensor'] = payload[8] data['sensor'] = payload[8]
@ -675,13 +668,13 @@ class hysen(device):
data['dif'] = payload[10] data['dif'] = payload[10]
data['svh'] = payload[11] data['svh'] = payload[11]
data['svl'] = payload[12] data['svl'] = payload[12]
data['room_temp_adj'] = ((payload[13] << 8) + payload[14])/2.0 data['room_temp_adj'] = ((payload[13] << 8) + payload[14]) / 2.0
if data['room_temp_adj'] > 32767: if data['room_temp_adj'] > 32767:
data['room_temp_adj'] = 32767 - data['room_temp_adj'] data['room_temp_adj'] = 32767 - data['room_temp_adj']
data['fre'] = payload[15] data['fre'] = payload[15]
data['poweron'] = payload[16] data['poweron'] = payload[16]
data['unknown'] = payload[17] data['unknown'] = payload[17]
data['external_temp'] = (payload[18] & 255)/2.0 data['external_temp'] = (payload[18] & 255) / 2.0
data['hour'] = payload[19] data['hour'] = payload[19]
data['min'] = payload[20] data['min'] = payload[20]
data['sec'] = payload[21] data['sec'] = payload[21]
@ -689,42 +682,49 @@ class hysen(device):
weekday = [] weekday = []
for i in range(0, 6): for i in range(0, 6):
weekday.append({'start_hour':payload[2*i + 23], 'start_minute':payload[2*i + 24],'temp':payload[i + 39]/2.0}) weekday.append(
{'start_hour': payload[2 * i + 23], 'start_minute': payload[2 * i + 24], 'temp': payload[i + 39] / 2.0})
data['weekday'] = weekday data['weekday'] = weekday
weekend = [] weekend = []
for i in range(6, 8): for i in range(6, 8):
weekend.append({'start_hour':payload[2*i + 23], 'start_minute':payload[2*i + 24],'temp':payload[i + 39]/2.0}) weekend.append(
{'start_hour': payload[2 * i + 23], 'start_minute': payload[2 * i + 24], 'temp': payload[i + 39] / 2.0})
data['weekend'] = weekend data['weekend'] = weekend
return data return data
# Change controller mode # Change controller mode
# auto_mode = 1 for auto (scheduled/timed) mode, 0 for manual mode. # auto_mode = 1 for auto (scheduled/timed) mode, 0 for manual mode.
# Manual mode will activate last used temperature. In typical usage call set_temp to activate manual control and set temp. # Manual mode will activate last used temperature.
# In typical usage call set_temp to activate manual control and set temp.
# loop_mode refers to index in [ "12345,67", "123456,7", "1234567" ] # loop_mode refers to index in [ "12345,67", "123456,7", "1234567" ]
# E.g. loop_mode = 0 ("12345,67") means Saturday and Sunday follow the "weekend" schedule # E.g. loop_mode = 0 ("12345,67") means Saturday and Sunday follow the "weekend" schedule
# loop_mode = 2 ("1234567") means every day (including Saturday and Sunday) follows the "weekday" schedule # loop_mode = 2 ("1234567") means every day (including Saturday and Sunday) follows the "weekday" schedule
# The sensor command is currently experimental # The sensor command is currently experimental
def set_mode(self, auto_mode, loop_mode,sensor=0): def set_mode(self, auto_mode, loop_mode, sensor=0):
mode_byte = ( (loop_mode + 1) << 4) + auto_mode mode_byte = ((loop_mode + 1) << 4) + auto_mode
# print 'Mode byte: 0x'+ format(mode_byte, '02x') # print 'Mode byte: 0x'+ format(mode_byte, '02x')
self.send_request(bytearray([0x01,0x06,0x00,0x02,mode_byte,sensor])) self.send_request(bytearray([0x01, 0x06, 0x00, 0x02, mode_byte, sensor]))
# Advanced settings # Advanced settings
# Sensor mode (SEN) sensor = 0 for internal sensor, 1 for external sensor, 2 for internal control temperature, external limit temperature. Factory default: 0. # Sensor mode (SEN) sensor = 0 for internal sensor, 1 for external sensor,
# 2 for internal control temperature, external limit temperature. Factory default: 0.
# Set temperature range for external sensor (OSV) osv = 5..99. Factory default: 42C # Set temperature range for external sensor (OSV) osv = 5..99. Factory default: 42C
# Deadzone for floor temprature (dIF) dif = 1..9. Factory default: 2C # Deadzone for floor temprature (dIF) dif = 1..9. Factory default: 2C
# Upper temperature limit for internal sensor (SVH) svh = 5..99. Factory default: 35C # Upper temperature limit for internal sensor (SVH) svh = 5..99. Factory default: 35C
# Lower temperature limit for internal sensor (SVL) svl = 5..99. Factory default: 5C # Lower temperature limit for internal sensor (SVL) svl = 5..99. Factory default: 5C
# Actual temperature calibration (AdJ) adj = -0.5. Prescision 0.1C # Actual temperature calibration (AdJ) adj = -0.5. Prescision 0.1C
# Anti-freezing function (FrE) fre = 0 for anti-freezing function shut down, 1 for anti-freezing function open. Factory default: 0 # Anti-freezing function (FrE) fre = 0 for anti-freezing function shut down,
# 1 for anti-freezing function open. Factory default: 0
# Power on memory (POn) poweron = 0 for power on memory off, 1 for power on memory on. Factory default: 0 # Power on memory (POn) poweron = 0 for power on memory off, 1 for power on memory on. Factory default: 0
def set_advanced(self, loop_mode, sensor, osv, dif, svh, svl, adj, fre, poweron): def set_advanced(self, loop_mode, sensor, osv, dif, svh, svl, adj, fre, poweron):
input_payload = bytearray([0x01,0x10,0x00,0x02,0x00,0x05,0x0a, loop_mode, sensor, osv, dif, svh, svl, (int(adj*2)>>8 & 0xff), (int(adj*2) & 0xff), fre, poweron]) input_payload = bytearray([0x01, 0x10, 0x00, 0x02, 0x00, 0x05, 0x0a, loop_mode, sensor, osv, dif, svh, svl,
(int(adj * 2) >> 8 & 0xff), (int(adj * 2) & 0xff), fre, poweron])
self.send_request(input_payload) self.send_request(input_payload)
# For backwards compatibility only. Prefer calling set_mode directly. Note this function invokes loop_mode=0 and sensor=0. # For backwards compatibility only. Prefer calling set_mode directly.
# Note this function invokes loop_mode=0 and sensor=0.
def switch_to_auto(self): def switch_to_auto(self):
self.set_mode(auto_mode=1, loop_mode=0) self.set_mode(auto_mode=1, loop_mode=0)
@ -733,16 +733,17 @@ class hysen(device):
# Set temperature for manual mode (also activates manual mode if currently in automatic) # Set temperature for manual mode (also activates manual mode if currently in automatic)
def set_temp(self, temp): def set_temp(self, temp):
self.send_request(bytearray([0x01,0x06,0x00,0x01,0x00,int(temp * 2)]) ) self.send_request(bytearray([0x01, 0x06, 0x00, 0x01, 0x00, int(temp * 2)]))
# Set device on(1) or off(0), does not deactivate Wifi connectivity. Remote lock disables control by buttons on thermostat. # Set device on(1) or off(0), does not deactivate Wifi connectivity.
# Remote lock disables control by buttons on thermostat.
def set_power(self, power=1, remote_lock=0): def set_power(self, power=1, remote_lock=0):
self.send_request(bytearray([0x01,0x06,0x00,0x00,remote_lock,power]) ) self.send_request(bytearray([0x01, 0x06, 0x00, 0x00, remote_lock, power]))
# set time on device # set time on device
# n.b. day=1 is Monday, ..., day=7 is Sunday # n.b. day=1 is Monday, ..., day=7 is Sunday
def set_time(self, hour, minute, second, day): def set_time(self, hour, minute, second, day):
self.send_request(bytearray([0x01,0x10,0x00,0x08,0x00,0x02,0x04, hour, minute, second, day ])) self.send_request(bytearray([0x01, 0x10, 0x00, 0x08, 0x00, 0x02, 0x04, hour, minute, second, day]))
# Set timer schedule # Set timer schedule
# Format is the same as you get from get_full_status. # Format is the same as you get from get_full_status.
@ -750,28 +751,28 @@ class hysen(device):
# {'start_hour':17, 'start_minute':30, 'temp': 22 } # {'start_hour':17, 'start_minute':30, 'temp': 22 }
# Each one specifies the thermostat temp that will become effective at start_hour:start_minute # Each one specifies the thermostat temp that will become effective at start_hour:start_minute
# weekend is similar but only has 2 (e.g. switch on in morning and off in afternoon) # weekend is similar but only has 2 (e.g. switch on in morning and off in afternoon)
def set_schedule(self,weekday,weekend): def set_schedule(self, weekday, weekend):
# Begin with some magic values ... # Begin with some magic values ...
input_payload = bytearray([0x01,0x10,0x00,0x0a,0x00,0x0c,0x18]) input_payload = bytearray([0x01, 0x10, 0x00, 0x0a, 0x00, 0x0c, 0x18])
# Now simply append times/temps # Now simply append times/temps
# weekday times # weekday times
for i in range(0, 6): for i in range(0, 6):
input_payload.append( weekday[i]['start_hour'] ) input_payload.append(weekday[i]['start_hour'])
input_payload.append( weekday[i]['start_minute'] ) input_payload.append(weekday[i]['start_minute'])
# weekend times # weekend times
for i in range(0, 2): for i in range(0, 2):
input_payload.append( weekend[i]['start_hour'] ) input_payload.append(weekend[i]['start_hour'])
input_payload.append( weekend[i]['start_minute'] ) input_payload.append(weekend[i]['start_minute'])
# weekday temperatures # weekday temperatures
for i in range(0, 6): for i in range(0, 6):
input_payload.append( int(weekday[i]['temp'] * 2) ) input_payload.append(int(weekday[i]['temp'] * 2))
# weekend temperatures # weekend temperatures
for i in range(0, 2): for i in range(0, 2):
input_payload.append( int(weekend[i]['temp'] * 2) ) input_payload.append(int(weekend[i]['temp'] * 2))
self.send_request(input_payload) self.send_request(input_payload)
@ -787,6 +788,7 @@ class S1C(device):
""" """
Its VERY VERY VERY DIRTY IMPLEMENTATION of S1C Its VERY VERY VERY DIRTY IMPLEMENTATION of S1C
""" """
def __init__(self, *a, **kw): def __init__(self, *a, **kw):
device.__init__(self, *a, **kw) device.__init__(self, *a, **kw)
self.type = 'S1C' self.type = 'S1C'
@ -796,13 +798,14 @@ class S1C(device):
packet[0] = 0x06 # 0x06 - get sensors info, 0x07 - probably add sensors packet[0] = 0x06 # 0x06 - get sensors info, 0x07 - probably add sensors
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
aes = AES.new(bytes(self.key), AES.MODE_CBC, bytes(self.iv)) aes = AES.new(bytes(self.key), AES.MODE_CBC, bytes(self.iv))
payload = aes.decrypt(bytes(response[0x38:])) payload = aes.decrypt(bytes(response[0x38:]))
if payload: if not payload:
head = payload[:4] return None
count = payload[0x4] #need to fix for python 2.x count = payload[0x4]
sensors = payload[0x6:] sensors = payload[0x6:]
sensors_a = [bytearray(sensors[i * 83:(i + 1) * 83]) for i in range(len(sensors) // 83)] sensors_a = [bytearray(sensors[i * 83:(i + 1) * 83]) for i in range(len(sensors) // 83)]
@ -812,7 +815,7 @@ class S1C(device):
_name = str(bytes(sens[4:26]).decode()) _name = str(bytes(sens[4:26]).decode())
_order = ord(chr(sens[1])) _order = ord(chr(sens[1]))
_type = ord(chr(sens[3])) _type = ord(chr(sens[3]))
_serial = bytes(codecs.encode(sens[26:30],"hex")).decode() _serial = bytes(codecs.encode(sens[26:30], "hex")).decode()
type_str = S1C_SENSORS_TYPES.get(_type, 'Unknown') type_str = S1C_SENSORS_TYPES.get(_type, 'Unknown')
@ -833,7 +836,7 @@ class S1C(device):
class dooya(device): class dooya(device):
def __init__ (self, host, mac, devtype): def __init__(self, host, mac, devtype):
device.__init__(self, host, mac, devtype) device.__init__(self, host, mac, devtype)
self.type = "Dooya DT360E" self.type = "Dooya DT360E"
@ -847,7 +850,8 @@ class dooya(device):
packet[10] = 0x44 packet[10] = 0x44
response = self.send_packet(0x6a, packet) response = self.send_packet(0x6a, packet)
err = response[0x22] | (response[0x23] << 8) err = response[0x22] | (response[0x23] << 8)
if err == 0: if err != 0:
return None
payload = self.decrypt(bytes(response[0x38:])) payload = self.decrypt(bytes(response[0x38:]))
return ord(payload[4]) return ord(payload[4])