diff options
-rw-r--r-- | README.md | 37 | ||||
-rw-r--r-- | broadlink/__init__.py | 396 | ||||
-rw-r--r-- | cli/README.md | 7 | ||||
-rwxr-xr-x | cli/broadlink_cli | 64 | ||||
-rwxr-xr-x | cli/broadlink_discovery | 8 | ||||
-rw-r--r-- | requirements.txt | 2 | ||||
-rw-r--r-- | setup.py | 9 |
7 files changed, 420 insertions, 103 deletions
diff --git a/README.md b/README.md index 48708827ce36..d5e0154db221 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ Python control for Broadlink RM2 IR controllers =============================================== -A simple Python API for controlling IR controllers from [Broadlink](http://www.ibroadlink.com/rm/). At present, only RM Pro (referred to as RM2 in the codebase) and A1 sensor platform devices are supported. There is currently no support for the cloud API. +A simple Python API for controlling IR controllers from [Broadlink](http://www.ibroadlink.com/rm/). At present, the following devices are currently supported: + +* RM Pro (referred to as RM2 in the codebase) +* A1 sensor platform devices are supported +* RM3 mini IR blaster + +There is currently no support for the cloud API. Example use ----------- @@ -42,6 +48,10 @@ Sweep RF frequencies: devices[0].sweep_frequency() ``` +Cancel sweep RF frequencies: +``` +devices[0].cancel_sweep_frequency() +``` Check whether a frequency has been found: ``` found = devices[0].check_frequency() @@ -85,6 +95,11 @@ Check power state on a SmartPlug: state = devices[0].check_power() ``` +Check energy consumption on a SmartPlug: +``` +state = devices[0].get_energy() +``` + Set power state for S1 on a SmartPowerStrip MP1: ``` devices[0].set_power(1, True) @@ -94,23 +109,3 @@ Check power state on a SmartPowerStrip: ``` state = devices[0].check_power() ``` - -Learning RF packets -------------------- - -timeout = 10 -devices[0].sweep_frequency() -# Hold down the rf button -for i in range(0, timeout): - found = devices[0].check_frequency() - if found == True: - break - time.sleep(1) -# Tap the rf button -for i in range(0, timeout): - found = devices[0].find_rf_packet() - if found == True: - break - time.sleep(1) -# Obtain the code -code = devices[0].check_data() diff --git a/broadlink/__init__.py b/broadlink/__init__.py index 8a193d556101..33bf5e8f879b 100644 --- a/broadlink/__init__.py +++ b/broadlink/__init__.py @@ -13,53 +13,50 @@ import sys import threading import codecs + def gendevice(devtype, host, mac): - if devtype == 0: # SP1 - return sp1(host=host, mac=mac) - if devtype == 0x2711: # SP2 - return sp2(host=host, mac=mac) - if devtype == 0x2719 or devtype == 0x7919 or devtype == 0x271a or devtype == 0x791a: # Honeywell SP2 - return sp2(host=host, mac=mac) - if devtype == 0x2720: # SPMini - return sp2(host=host, mac=mac) - elif devtype == 0x753e: # SP3 - return sp2(host=host, mac=mac) - elif devtype == 0x947a or devtype == 0x9479: # SP3S - return sp2(host=host, mac=mac) - elif devtype == 0x2728: # SPMini2 - return sp2(host=host, mac=mac) - elif devtype == 0x2733 or devtype == 0x273e: # OEM branded SPMini - return sp2(host=host, mac=mac) - elif devtype >= 0x7530 and devtype <= 0x7918: # OEM branded SPMini2 - return sp2(host=host, mac=mac) - elif devtype == 0x2736: # SPMiniPlus - return sp2(host=host, mac=mac) - elif devtype == 0x2712: # RM2 - return rm(host=host, mac=mac) - elif devtype == 0x2737: # RM Mini - return rm(host=host, mac=mac) - elif devtype == 0x273d: # RM Pro Phicomm - return rm(host=host, mac=mac) - elif devtype == 0x2783: # RM2 Home Plus - return rm(host=host, mac=mac) - elif devtype == 0x277c: # RM2 Home Plus GDT - return rm(host=host, mac=mac) - elif devtype == 0x272a: # RM2 Pro Plus - return rm(host=host, mac=mac) - elif devtype == 0x2787: # RM2 Pro Plus2 - return rm(host=host, mac=mac) - elif devtype == 0x278b: # RM2 Pro Plus BL - return rm(host=host, mac=mac) - elif devtype == 0x278f: # RM Mini Shate - return rm(host=host, mac=mac) - elif devtype == 0x2714: # A1 - return a1(host=host, mac=mac) - elif devtype == 0x4EB5 or devtype == 0x4EF7: # MP1: 0x4eb5, honyar oem mp1: 0x4ef7 - return mp1(host=host, mac=mac) - elif devtype == 0x2722: # S1 (SmartOne Alarm Kit) - return S1C(host=host, mac=mac) - else: - return device(host=host, mac=mac) + devices = { + sp1: [0], + sp2: [0x2711, # SP2 + 0x2719, 0x7919, 0x271a, 0x791a, # Honeywell SP2 + 0x2720, # SPMini + 0x753e, # SP3 + 0x7D00, # OEM branded SP3 + 0x947a, 0x9479, # SP3S + 0x2728, # SPMini2 + 0x2733, 0x273e, # OEM branded SPMini + 0x7530, 0x7918, # OEM branded SPMini2 + 0x2736 # SPMiniPlus + ], + rm: [0x2712, # RM2 + 0x2737, # RM Mini + 0x273d, # RM Pro Phicomm + 0x2783, # RM2 Home Plus + 0x277c, # RM2 Home Plus GDT + 0x272a, # RM2 Pro Plus + 0x2787, # RM2 Pro Plus2 + 0x279d, # RM2 Pro Plus3 + 0x27a9, # RM2 Pro Plus_300 + 0x278b, # RM2 Pro Plus BL + 0x2797, # RM2 Pro Plus HYC + 0x27a1, # RM2 Pro Plus R1 + 0x27a6, # RM2 Pro PP + 0x278f # RM Mini Shate + ], + a1: [0x2714], # A1 + mp1: [0x4EB5, # MP1 + 0x4EF7 # Honyar oem mp1 + ], + hysen: [0x4EAD], # Hysen controller + S1C: [0x2722], # S1 (SmartOne Alarm Kit) + dooya: [0x4E4D] # Dooya DT360E (DOOYA_CURTAIN_V2) + } + + # Look for the class associated to devtype in devices + [deviceClass] = [dev for dev in devices if devtype in devices[dev]] or [None] + if deviceClass is None: + return device(host=host, mac=mac, devtype=devtype) + return deviceClass(host=host, mac=mac, devtype=devtype) def discover(timeout=None, local_ip_address=None): if local_ip_address is None: @@ -122,6 +119,8 @@ def discover(timeout=None, local_ip_address=None): host = response[1] mac = responsepacket[0x3a:0x40] devtype = responsepacket[0x34] | responsepacket[0x35] << 8 + + return gendevice(devtype, host, mac) else: while (time.time() - starttime) < timeout: @@ -139,10 +138,12 @@ def discover(timeout=None, local_ip_address=None): return devices + class device: - def __init__(self, host, mac, timeout=10): + def __init__(self, host, mac, devtype, timeout=10): self.host = host self.mac = mac + self.devtype = devtype self.timeout = timeout self.count = random.randrange(0xffff) self.key = bytearray([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]) @@ -155,7 +156,7 @@ class device: self.type = "Unknown" self.lock = threading.Lock() - if 'pyaes' in sys.modules: + if 'pyaes' in globals(): self.encrypt = self.encrypt_pyaes self.decrypt = self.decrypt_pyaes else: @@ -164,11 +165,11 @@ class device: def encrypt_pyaes(self, payload): aes = pyaes.AESModeOfOperationCBC(self.key, iv = bytes(self.iv)) - return "".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): aes = pyaes.AESModeOfOperationCBC(self.key, iv = bytes(self.iv)) - return "".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): aes = AES.new(bytes(self.key), AES.MODE_CBC, bytes(self.iv)) @@ -218,6 +219,7 @@ class device: self.id = payload[0x00:0x04] self.key = key + return True def get_type(self): @@ -253,7 +255,7 @@ class device: # pad the payload for AES encryption if len(payload)>0: numpad=(len(payload)//16+1)*16 - payload=payload.ljust(numpad,b"\x00") + payload=payload.ljust(numpad, b"\x00") checksum = 0xbeaf for i in range(len(payload)): @@ -290,8 +292,8 @@ class device: class mp1(device): - def __init__ (self, host, mac): - device.__init__(self, host, mac) + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) self.type = "MP1" def set_power_mask(self, sid_mask, state): @@ -353,8 +355,8 @@ class mp1(device): class sp1(device): - def __init__ (self, host, mac): - device.__init__(self, host, mac) + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) self.type = "SP1" def set_power(self, state): @@ -364,15 +366,28 @@ class sp1(device): class sp2(device): - def __init__ (self, host, mac): - device.__init__(self, host, mac) + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) self.type = "SP2" def set_power(self, state): """Sets the power state of the smart plug.""" packet = bytearray(16) packet[0] = 2 - packet[4] = 1 if state else 0 + if self.check_nightlight(): + packet[4] = 3 if state else 2 + else: + packet[4] = 1 if state else 0 + self.send_packet(0x6a, packet) + + def set_nightlight(self, state): + """Sets the night light state of the smart plug""" + packet = bytearray(16) + packet[0] = 2 + if self.check_power(): + packet[4] = 3 if state else 1 + else: + packet[4] = 2 if state else 0 self.send_packet(0x6a, packet) def check_power(self): @@ -384,9 +399,35 @@ class sp2(device): if err == 0: payload = self.decrypt(bytes(response[0x38:])) if type(payload[0x4]) == int: - state = bool(payload[0x4]) + if payload[0x4] == 1 or payload[0x4] == 3 or payload[0x4] == 0xFD: + state = True + else: + state = False else: - state = bool(ord(payload[0x4])) + 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): + """Returns the power state of the smart plug.""" + packet = bytearray(16) + packet[0] = 1 + response = self.send_packet(0x6a, packet) + err = response[0x22] | (response[0x23] << 8) + if err == 0: + payload = self.decrypt(bytes(response[0x38:])) + if type(payload[0x4]) == int: + if payload[0x4] == 2 or payload[0x4] == 3 or payload[0x4] == 0xFF: + state = True + 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): @@ -395,13 +436,16 @@ class sp2(device): err = response[0x22] | (response[0x23] << 8) if err == 0: payload = self.decrypt(bytes(response[0x38:])) - energy = int(hex(ord(payload[7]) * 256 + ord(payload[6]))[2:]) + int(hex(ord(payload[5]))[2:])/100.0 + if type(payload[0x07]) == int: + energy = int(hex(payload[0x07] * 256 + payload[0x06])[2:]) + int(hex(payload[0x05])[2:])/100.0 + else: + energy = int(hex(ord(payload[0x07]) * 256 + ord(payload[0x06]))[2:]) + int(hex(ord(payload[0x05]))[2:])/100.0 return energy class a1(device): - def __init__ (self, host, mac): - device.__init__(self, host, mac) + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) self.type = "A1" def check_sensors(self): @@ -478,8 +522,8 @@ class a1(device): class rm(device): - def __init__ (self, host, mac): - device.__init__(self, host, mac) + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) self.type = "RM2" def check_data(self): @@ -546,10 +590,11 @@ class rm(device): temp = (ord(payload[0x4]) * 10 + ord(payload[0x5])) / 10.0 return temp + # For legacy compatibility - don't use this class rm2(rm): def __init__ (self): - device.__init__(self, None, None) + device.__init__(self, None, None, None) def discover(self): dev = discover() @@ -557,6 +602,178 @@ class rm2(rm): self.mac = dev.mac +class hysen(device): + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) + self.type = "Hysen heating controller" + + # Send a request + # input_payload should be a bytearray, usually 6 bytes, e.g. bytearray([0x01,0x06,0x00,0x02,0x10,0x00]) + # Returns decrypted payload + # 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 + def send_request(self,input_payload): + + from PyCRC.CRC16 import CRC16 + crc = CRC16(modbus_flag=True).calculate(bytes(input_payload)) + + # first byte is length, +2 for CRC16 + request_payload = bytearray([len(input_payload) + 2,0x00]) + request_payload.extend(input_payload) + + # append CRC + request_payload.append(crc & 0xFF) + request_payload.append((crc >> 8) & 0xFF) + + # send to device + response = self.send_packet(0x6a, request_payload) + + # check for error + err = response[0x22] | (response[0x23] << 8) + if err: + raise ValueError('broadlink_response_error',err) + + response_payload = bytearray(self.decrypt(bytes(response[0x38:]))) + + # experimental check on CRC in response (first 2 bytes are len, and trailing bytes are crc) + response_payload_len = response_payload[0] + if response_payload_len + 2 > len(response_payload): + 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])) + 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] + else: + raise ValueError('hysen_response_error','CRC check on response failed') + + + # Get current room temperature in degrees celsius + def get_temp(self): + payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x08])) + return payload[0x05] / 2.0 + + # Get current external temperature in degrees celsius + def get_external_temp(self): + payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x08])) + return payload[18] / 2.0 + + # Get full status (including timer schedule) + def get_full_status(self): + payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x16])) + data = {} + data['remote_lock'] = payload[3] & 1 + data['power'] = payload[4] & 1 + data['active'] = (payload[4] >> 4) & 1 + data['temp_manual'] = (payload[4] >> 6) & 1 + data['room_temp'] = (payload[5] & 255)/2.0 + data['thermostat_temp'] = (payload[6] & 255)/2.0 + data['auto_mode'] = payload[7] & 15 + data['loop_mode'] = (payload[7] >> 4) & 15 + data['sensor'] = payload[8] + data['osv'] = payload[9] + data['dif'] = payload[10] + data['svh'] = payload[11] + data['svl'] = payload[12] + data['room_temp_adj'] = ((payload[13] << 8) + payload[14])/2.0 + if data['room_temp_adj'] > 32767: + data['room_temp_adj'] = 32767 - data['room_temp_adj'] + data['fre'] = payload[15] + data['poweron'] = payload[16] + data['unknown'] = payload[17] + data['external_temp'] = (payload[18] & 255)/2.0 + data['hour'] = payload[19] + data['min'] = payload[20] + data['sec'] = payload[21] + data['dayofweek'] = payload[22] + + weekday = [] + 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}) + + data['weekday'] = weekday + weekend = [] + 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}) + + data['weekend'] = weekend + return data + + # Change controller 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. + # 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 + # loop_mode = 2 ("1234567") means every day (including Saturday and Sunday) follows the "weekday" schedule + # The sensor command is currently experimental + def set_mode(self, auto_mode, loop_mode,sensor=0): + mode_byte = ( (loop_mode + 1) << 4) + auto_mode + # print 'Mode byte: 0x'+ format(mode_byte, '02x') + self.send_request(bytearray([0x01,0x06,0x00,0x02,mode_byte,sensor])) + + # 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. + # Set temperature range for external sensor (OSV) osv = 5..99. Factory default: 42C + # Deadzone for floor temprature (dIF) dif = 1..9. Factory default: 2C + # 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 + # 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 + # 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): + 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) + + # For backwards compatibility only. Prefer calling set_mode directly. Note this function invokes loop_mode=0 and sensor=0. + def switch_to_auto(self): + self.set_mode(auto_mode=1, loop_mode=0) + + def switch_to_manual(self): + self.set_mode(auto_mode=0, loop_mode=0) + + # Set temperature for manual mode (also activates manual mode if currently in automatic) + def set_temp(self, temp): + 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. + def set_power(self, power=1, remote_lock=0): + self.send_request(bytearray([0x01,0x06,0x00,0x00,remote_lock,power]) ) + + # set time on device + # n.b. day=1 is Monday, ..., day=7 is Sunday + def set_time(self, hour, minute, second, day): + self.send_request(bytearray([0x01,0x10,0x00,0x08,0x00,0x02,0x04, hour, minute, second, day ])) + + # Set timer schedule + # Format is the same as you get from get_full_status. + # weekday is a list (ordered) of 6 dicts like: + # {'start_hour':17, 'start_minute':30, 'temp': 22 } + # 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) + def set_schedule(self,weekday,weekend): + # Begin with some magic values ... + input_payload = bytearray([0x01,0x10,0x00,0x0a,0x00,0x0c,0x18]) + + # Now simply append times/temps + # weekday times + for i in range(0, 6): + input_payload.append( weekday[i]['start_hour'] ) + input_payload.append( weekday[i]['start_minute'] ) + + # weekend times + for i in range(0, 2): + input_payload.append( weekend[i]['start_hour'] ) + input_payload.append( weekend[i]['start_minute'] ) + + # weekday temperatures + for i in range(0, 6): + input_payload.append( int(weekday[i]['temp'] * 2) ) + + # weekend temperatures + for i in range(0, 2): + input_payload.append( int(weekend[i]['temp'] * 2) ) + + self.send_request(input_payload) + + S1C_SENSORS_TYPES = { 0x31: 'Door Sensor', # 49 as hex 0x91: 'Key Fob', # 145 as hex, as serial on fob corpse @@ -613,6 +830,53 @@ class S1C(device): return result +class dooya(device): + def __init__ (self, host, mac, devtype): + device.__init__(self, host, mac, devtype) + self.type = "Dooya DT360E" + + def _send(self, magic1, magic2): + packet = bytearray(16) + packet[0] = 0x09 + packet[2] = 0xbb + packet[3] = magic1 + packet[4] = magic2 + packet[9] = 0xfa + packet[10] = 0x44 + response = self.send_packet(0x6a, packet) + err = response[0x22] | (response[0x23] << 8) + if err == 0: + payload = self.decrypt(bytes(response[0x38:])) + return ord(payload[4]) + + def open(self): + return self._send(0x01, 0x00) + + def close(self): + return self._send(0x02, 0x00) + + def stop(self): + return self._send(0x03, 0x00) + + def get_percentage(self): + return self._send(0x06, 0x5d) + + def set_percentage_and_wait(self, new_percentage): + current = self.get_percentage() + if current > new_percentage: + self.close() + while current is not None and current > new_percentage: + time.sleep(0.2) + current = self.get_percentage() + + elif current < new_percentage: + self.open() + while current is not None and current < new_percentage: + time.sleep(0.2) + current = self.get_percentage() + self.stop() + + # Setup a new Broadlink device via AP Mode. Review the README to see how to enter AP Mode. # Only tested with Broadlink RM3 Mini (Blackbean) def setup(ssid, password, security_mode): diff --git a/cli/README.md b/cli/README.md index 47b45f4c97d3..5d7b3be19302 100644 --- a/cli/README.md +++ b/cli/README.md @@ -13,7 +13,7 @@ You should have the broadlink python installed, this can be made in many linux d sudo pip install broadlink ``` -Instalation +Installation ----------- Just copy this files @@ -73,3 +73,8 @@ Get Temperature : ``` broadlink_cli --device @BEDROOM.device --temperature ``` + +Get Energy Consumption (For a SmartPlug) : +``` +broadlink_cli --device @BEDROOM.device --energy +``` diff --git a/cli/broadlink_cli b/cli/broadlink_cli index 1ff45f1d9b3c..989c6c118ba7 100755 --- a/cli/broadlink_cli +++ b/cli/broadlink_cli @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import broadlink +import sys import argparse import time @@ -67,11 +68,19 @@ parser.add_argument("--type", type=auto_int, default=0x2712, help="type of devic parser.add_argument("--host", help="host address") parser.add_argument("--mac", help="mac address (hex reverse), as used by python-broadlink library") parser.add_argument("--temperature",action="store_true", help="request temperature from device") +parser.add_argument("--energy",action="store_true", help="request energy consumption from device") +parser.add_argument("--check", action="store_true", help="check current power state") +parser.add_argument("--checknl", action="store_true", help="check current nightlight state") +parser.add_argument("--turnon", action="store_true", help="turn on device") +parser.add_argument("--turnoff", action="store_true", help="turn off device") +parser.add_argument("--turnnlon", action="store_true", help="turn on nightlight on the device") +parser.add_argument("--turnnloff", action="store_true", help="turn off nightlight on the device") +parser.add_argument("--switch", action="store_true", help="switch state from on to off and off to on") parser.add_argument("--send", action="store_true", help="send command") parser.add_argument("--sensors", action="store_true", help="check all sensors") parser.add_argument("--learn", action="store_true", help="learn command") -parser.add_argument("--learnfile", help="save learned command to a specified file") parser.add_argument("--rfscanlearn", action="store_true", help="rf scan learning") +parser.add_argument("--learnfile", help="save learned command to a specified file") parser.add_argument("--durations", action="store_true", help="use durations in micro seconds instead of the Broadlink format") parser.add_argument("--convert", action="store_true", help="convert input data to durations") parser.add_argument("data", nargs='*', help="Data to send or convert") @@ -79,7 +88,7 @@ args = parser.parse_args() if args.device: values = args.device.split() - type = int(values[0], 0) + type = int(values[0],0) host = values[1] mac = bytearray.fromhex(values[2]) elif args.mac: @@ -87,7 +96,7 @@ elif args.mac: host = args.host mac = bytearray.fromhex(args.mac) -if args.host or host is not None: +if args.host or args.device: dev = broadlink.gendevice(type, (host, 80), mac) dev.auth() @@ -97,6 +106,8 @@ if args.convert: print(format_durations(durations)) if args.temperature: print(dev.check_temperature()) +if args.energy: + print(dev.get_energy()) if args.sensors: try: data = dev.check_sensors() @@ -122,14 +133,55 @@ if args.learn: learned = format_durations(to_microseconds(bytearray(data))) \ if args.durations \ else ''.join(format(x, '02x') for x in bytearray(data)) - if args.learnfile is None: + if args.learn: print(learned) - if args.learnfile is not None: + if args.learnfile: print("Saving to {}".format(args.learnfile)) with open(args.learnfile, "w") as text_file: text_file.write(learned) else: print("No data received...") +if args.check: + if dev.check_power(): + print('* ON *') + else: + print('* OFF *') +if args.checknl: + if dev.check_nightlight(): + print('* ON *') + else: + print('* OFF *') +if args.turnon: + dev.set_power(True) + if dev.check_power(): + print('== Turned * ON * ==') + else: + print('!! Still OFF !!') +if args.turnoff: + dev.set_power(False) + if dev.check_power(): + print('!! Still ON !!') + else: + print('== Turned * OFF * ==') +if args.turnnlon: + dev.set_nightlight(True) + if dev.check_nightlight(): + print('== Turned * ON * ==') + else: + print('!! Still OFF !!') +if args.turnnloff: + dev.set_nightlight(False) + if dev.check_nightlight(): + print('!! Still ON !!') + else: + print('== Turned * OFF * ==') +if args.switch: + if dev.check_power(): + dev.set_power(False) + print('* Switch to OFF *') + else: + dev.set_power(True) + print('* Switch to ON *') if args.rfscanlearn: dev.sweep_frequency() print("Learning RF Frequency, press and hold the button to learn...") @@ -174,4 +226,4 @@ if args.rfscanlearn: with open(args.learnfile, "w") as text_file: text_file.write(learned) else: - print("No data received...") \ No newline at end of file + print("No data received...") diff --git a/cli/broadlink_discovery b/cli/broadlink_discovery index 4a7438dfb7a4..385f1932467f 100755 --- a/cli/broadlink_discovery +++ b/cli/broadlink_discovery @@ -8,17 +8,15 @@ parser = argparse.ArgumentParser(fromfile_prefix_chars='@'); parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses") args = parser.parse_args() -print "discover" +print "Discovering..." devices = broadlink.discover(timeout=args.timeout) -#print devices for device in devices: if device.auth(): print "###########################################" -# print device print device.type - print "# broadlink_cli --type 0x2712 --host {} --mac {}".format(device.host[0], ''.join(format(x, '02x') for x in device.mac)) + print "# broadlink_cli --type {} --host {} --mac {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac)) print "Device file data (to be used with --device @filename in broadlink_cli) : " - print "0x2712 {} {}".format(device.host[0], ''.join(format(x, '02x') for x in device.mac)) + print "{} {} {}".format(hex(device.devtype), device.host[0], ''.join(format(x, '02x') for x in device.mac)) if hasattr(device, 'check_temperature'): print "temperature = {}".format(device.check_temperature()) print "" diff --git a/requirements.txt b/requirements.txt index f7d3e237ac67..9b20c33eb44b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pycrypto==2.6.1 +pycryptodome==3.6.6 diff --git a/setup.py b/setup.py index f8d1f4cab807..f4482eff56d1 100644 --- a/setup.py +++ b/setup.py @@ -10,13 +10,16 @@ try: import pyaes dynamic_requires = ["pyaes==1.6.0"] except ImportError as e: - dynamic_requires = ['pycrypto==2.6.1'] + dynamic_requires = ['pycryptodome==3.6.6'] -version = 0.6 +# For Hysen thermostatic heating controller +dynamic_requires.append('PyCRC') + +version = 0.9 setup( name='broadlink', - version=0.6, + version=0.9, author='Matthew Garrett', author_email='mjg59@srcf.ucam.org', url='http://github.com/mjg59/python-broadlink', |