Add 'third_party/python/broadlink/' from commit '17968ef4d40e7cb484909b3ddad33d7795b80091'
git-subtree-dir: third_party/python/broadlink git-subtree-mainline:0f53060159
git-subtree-split:17968ef4d4
This commit is contained in:
commit
9d94a727b2
10 changed files with 1836 additions and 0 deletions
1
third_party/python/broadlink/.gitignore
vendored
Normal file
1
third_party/python/broadlink/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.pyc
|
22
third_party/python/broadlink/LICENSE
vendored
Normal file
22
third_party/python/broadlink/LICENSE
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Mike Ryan
|
||||||
|
Copyright (c) 2016 Matthew Garrett
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
112
third_party/python/broadlink/README.md
vendored
Normal file
112
third_party/python/broadlink/README.md
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
Python control for Broadlink RM2, RM3 and RM4 series controllers
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
A simple Python API for controlling IR/RF 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
|
||||||
|
* RM4 and RM4C mini blasters
|
||||||
|
|
||||||
|
There is currently no support for the cloud API.
|
||||||
|
|
||||||
|
Example use
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Setup a new device on your local wireless network:
|
||||||
|
|
||||||
|
1. Put the device into AP Mode
|
||||||
|
1. Long press the reset button until the blue LED is blinking quickly.
|
||||||
|
2. Long press again until blue LED is blinking slowly.
|
||||||
|
3. Manually connect to the WiFi SSID named BroadlinkProv.
|
||||||
|
2. Run setup() and provide your ssid, network password (if secured), and set the security mode
|
||||||
|
1. Security mode options are (0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
|
||||||
|
```
|
||||||
|
import broadlink
|
||||||
|
|
||||||
|
broadlink.setup('myssid', 'mynetworkpass', 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
Discover available devices on the local network:
|
||||||
|
```
|
||||||
|
import broadlink
|
||||||
|
|
||||||
|
devices = broadlink.discover(timeout=5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Obtain the authentication key required for further communication:
|
||||||
|
```
|
||||||
|
devices[0].auth()
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter learning mode:
|
||||||
|
```
|
||||||
|
devices[0].enter_learning()
|
||||||
|
```
|
||||||
|
|
||||||
|
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()
|
||||||
|
```
|
||||||
|
(This will return True if the RM has locked onto a frequency, False otherwise)
|
||||||
|
|
||||||
|
Attempt to learn an RF packet:
|
||||||
|
```
|
||||||
|
found = devices[0].find_rf_packet()
|
||||||
|
```
|
||||||
|
(This will return True if a packet has been found, False otherwise)
|
||||||
|
|
||||||
|
Obtain an IR or RF packet while in learning mode:
|
||||||
|
```
|
||||||
|
ir_packet = devices[0].check_data()
|
||||||
|
```
|
||||||
|
(This will return None if the device does not have a packet to return)
|
||||||
|
|
||||||
|
Send an IR or RF packet:
|
||||||
|
```
|
||||||
|
devices[0].send_data(ir_packet)
|
||||||
|
```
|
||||||
|
|
||||||
|
Obtain temperature data from an RM2:
|
||||||
|
```
|
||||||
|
devices[0].check_temperature()
|
||||||
|
```
|
||||||
|
|
||||||
|
Obtain sensor data from an A1:
|
||||||
|
```
|
||||||
|
data = devices[0].check_sensors()
|
||||||
|
```
|
||||||
|
|
||||||
|
Set power state on a SmartPlug SP2/SP3:
|
||||||
|
```
|
||||||
|
devices[0].set_power(True)
|
||||||
|
```
|
||||||
|
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
|
||||||
|
Check power state on a SmartPowerStrip:
|
||||||
|
```
|
||||||
|
state = devices[0].check_power()
|
||||||
|
```
|
1118
third_party/python/broadlink/broadlink/__init__.py
vendored
Normal file
1118
third_party/python/broadlink/broadlink/__init__.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
85
third_party/python/broadlink/cli/README.md
vendored
Normal file
85
third_party/python/broadlink/cli/README.md
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
Command line interface for python-broadlink
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
This is a command line interface for broadlink python library
|
||||||
|
|
||||||
|
Tested with BroadLink RMPRO / RM2
|
||||||
|
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
You should have the broadlink python installed, this can be made in many linux distributions using :
|
||||||
|
```
|
||||||
|
sudo pip install broadlink
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation
|
||||||
|
-----------
|
||||||
|
Just copy this files
|
||||||
|
|
||||||
|
|
||||||
|
Programs
|
||||||
|
--------
|
||||||
|
|
||||||
|
|
||||||
|
* broadlink_discovery
|
||||||
|
used to run the discovery in the network
|
||||||
|
this program withh show the command line parameters to be used with
|
||||||
|
broadlink_cli to select broadlink device
|
||||||
|
|
||||||
|
* broadlink_cli
|
||||||
|
used to send commands and query the broadlink device
|
||||||
|
|
||||||
|
|
||||||
|
device specification formats
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Using separate parameters for each information:
|
||||||
|
```
|
||||||
|
broadlink_cli --type 0x2712 --host 1.1.1.1 --mac aaaaaaaaaa --temp
|
||||||
|
```
|
||||||
|
|
||||||
|
Using all parameters as a single argument:
|
||||||
|
```
|
||||||
|
broadlink_cli --device "0x2712 1.1.1.1 aaaaaaaaaa" --temp
|
||||||
|
```
|
||||||
|
|
||||||
|
Using file with parameters:
|
||||||
|
```
|
||||||
|
broadlink_cli --device @BEDROOM.device --temp
|
||||||
|
```
|
||||||
|
This is prefered as the configuration is stored in file and you can change
|
||||||
|
just a file to point to a different hardware
|
||||||
|
|
||||||
|
Sample usage
|
||||||
|
------------
|
||||||
|
|
||||||
|
Learn commands :
|
||||||
|
```
|
||||||
|
# Learn and save to file
|
||||||
|
broadlink_cli --device @BEDROOM.device --learnfile LG-TV.power
|
||||||
|
# LEard and show at console
|
||||||
|
broadlink_cli --device @BEDROOM.device --learn
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Send command :
|
||||||
|
```
|
||||||
|
broadlink_cli --device @BEDROOM.device --send @LG-TV.power
|
||||||
|
broadlink_cli --device @BEDROOM.device --send ....datafromlearncommand...
|
||||||
|
```
|
||||||
|
|
||||||
|
Get Temperature :
|
||||||
|
```
|
||||||
|
broadlink_cli --device @BEDROOM.device --temperature
|
||||||
|
```
|
||||||
|
|
||||||
|
Get Energy Consumption (For a SmartPlug) :
|
||||||
|
```
|
||||||
|
broadlink_cli --device @BEDROOM.device --energy
|
||||||
|
```
|
||||||
|
|
||||||
|
Once joined to the Broadlink provisioning Wi-Fi, configure it with your Wi-Fi details:
|
||||||
|
```
|
||||||
|
broadlink_cli --joinwifi MySSID MyWifiPassword
|
||||||
|
```
|
239
third_party/python/broadlink/cli/broadlink_cli
vendored
Executable file
239
third_party/python/broadlink/cli/broadlink_cli
vendored
Executable file
|
@ -0,0 +1,239 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import codecs
|
||||||
|
import time
|
||||||
|
|
||||||
|
import broadlink
|
||||||
|
|
||||||
|
TICK = 32.84
|
||||||
|
IR_TOKEN = 0x26
|
||||||
|
|
||||||
|
|
||||||
|
def auto_int(x):
|
||||||
|
return int(x, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def to_microseconds(bytes):
|
||||||
|
result = []
|
||||||
|
# print bytes[0] # 0x26 = 38for IR
|
||||||
|
index = 4
|
||||||
|
while index < len(bytes):
|
||||||
|
chunk = bytes[index]
|
||||||
|
index += 1
|
||||||
|
if chunk == 0:
|
||||||
|
chunk = bytes[index]
|
||||||
|
chunk = 256 * chunk + bytes[index + 1]
|
||||||
|
index += 2
|
||||||
|
result.append(int(round(chunk * TICK)))
|
||||||
|
if chunk == 0x0d05:
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def durations_to_broadlink(durations):
|
||||||
|
result = bytearray()
|
||||||
|
result.append(IR_TOKEN)
|
||||||
|
result.append(0)
|
||||||
|
result.append(len(durations) % 256)
|
||||||
|
result.append(len(durations) / 256)
|
||||||
|
for dur in durations:
|
||||||
|
num = int(round(dur / TICK))
|
||||||
|
if num > 255:
|
||||||
|
result.append(0)
|
||||||
|
result.append(num / 256)
|
||||||
|
result.append(num % 256)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def format_durations(data):
|
||||||
|
result = ''
|
||||||
|
for i in range(0, len(data)):
|
||||||
|
if len(result) > 0:
|
||||||
|
result += ' '
|
||||||
|
result += ('+' if i % 2 == 0 else '-') + str(data[i])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def parse_durations(str):
|
||||||
|
result = []
|
||||||
|
for s in str.split():
|
||||||
|
result.append(abs(int(s)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
|
||||||
|
parser.add_argument("--device", help="device definition as 'type host mac'")
|
||||||
|
parser.add_argument("--type", type=auto_int, default=0x2712, help="type of device")
|
||||||
|
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("--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("--joinwifi", nargs=2, help="Args are SSID PASSPHRASE to configure Broadlink device with");
|
||||||
|
parser.add_argument("data", nargs='*', help="Data to send or convert")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.device:
|
||||||
|
values = args.device.split()
|
||||||
|
type = int(values[0], 0)
|
||||||
|
host = values[1]
|
||||||
|
mac = bytearray.fromhex(values[2])
|
||||||
|
elif args.mac:
|
||||||
|
type = args.type
|
||||||
|
host = args.host
|
||||||
|
mac = bytearray.fromhex(args.mac)
|
||||||
|
|
||||||
|
if args.host or args.device:
|
||||||
|
dev = broadlink.gendevice(type, (host, 80), mac)
|
||||||
|
dev.auth()
|
||||||
|
|
||||||
|
if args.joinwifi:
|
||||||
|
broadlink.setup(args.joinwifi[0], args.joinwifi[1], 4)
|
||||||
|
|
||||||
|
if args.convert:
|
||||||
|
data = bytearray.fromhex(''.join(args.data))
|
||||||
|
durations = to_microseconds(data)
|
||||||
|
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()
|
||||||
|
except:
|
||||||
|
data = {}
|
||||||
|
data['temperature'] = dev.check_temperature()
|
||||||
|
for key in data:
|
||||||
|
print("{} {}".format(key, data[key]))
|
||||||
|
if args.send:
|
||||||
|
data = durations_to_broadlink(parse_durations(' '.join(args.data))) \
|
||||||
|
if args.durations else bytearray.fromhex(''.join(args.data))
|
||||||
|
dev.send_data(data)
|
||||||
|
if args.learn or args.learnfile:
|
||||||
|
dev.enter_learning()
|
||||||
|
data = None
|
||||||
|
print("Learning...")
|
||||||
|
timeout = 30
|
||||||
|
while (data is None) and (timeout > 0):
|
||||||
|
time.sleep(2)
|
||||||
|
timeout -= 2
|
||||||
|
data = dev.check_data()
|
||||||
|
if data:
|
||||||
|
learned = format_durations(to_microseconds(bytearray(data))) \
|
||||||
|
if args.durations \
|
||||||
|
else ''.join(format(x, '02x') for x in bytearray(data))
|
||||||
|
if args.learn:
|
||||||
|
print(learned)
|
||||||
|
decode_hex = codecs.getdecoder("hex_codec")
|
||||||
|
print("Base64: " + str(base64.b64encode(decode_hex(learned)[0])))
|
||||||
|
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...")
|
||||||
|
|
||||||
|
timeout = 20
|
||||||
|
|
||||||
|
while (not dev.check_frequency()) and (timeout > 0):
|
||||||
|
time.sleep(1)
|
||||||
|
timeout -= 1
|
||||||
|
|
||||||
|
if timeout <= 0:
|
||||||
|
print("RF Frequency not found")
|
||||||
|
dev.cancel_sweep_frequency()
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print("Found RF Frequency - 1 of 2!")
|
||||||
|
print("You can now let go of the button")
|
||||||
|
|
||||||
|
input("Press enter to continue...")
|
||||||
|
|
||||||
|
print("To complete learning, single press the button you want to learn")
|
||||||
|
|
||||||
|
dev.find_rf_packet()
|
||||||
|
|
||||||
|
data = None
|
||||||
|
timeout = 20
|
||||||
|
|
||||||
|
while (data is None) and (timeout > 0):
|
||||||
|
time.sleep(1)
|
||||||
|
timeout -= 1
|
||||||
|
data = dev.check_data()
|
||||||
|
|
||||||
|
if data:
|
||||||
|
print("Found RF Frequency - 2 of 2!")
|
||||||
|
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:
|
||||||
|
print(learned)
|
||||||
|
decode_hex = codecs.getdecoder("hex_codec")
|
||||||
|
print("Base64: {}".format(str(base64.b64encode(decode_hex(learned)[0]))))
|
||||||
|
if args.learnfile is not None:
|
||||||
|
print("Saving to {}".format(args.learnfile))
|
||||||
|
with open(args.learnfile, "w") as text_file:
|
||||||
|
text_file.write(learned)
|
||||||
|
else:
|
||||||
|
print("No data received...")
|
27
third_party/python/broadlink/cli/broadlink_discovery
vendored
Executable file
27
third_party/python/broadlink/cli/broadlink_discovery
vendored
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import broadlink
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
|
||||||
|
parser.add_argument("--timeout", type=int, default=5, help="timeout to wait for receiving discovery responses")
|
||||||
|
parser.add_argument("--ip", default=None, help="ip address to use in the discovery")
|
||||||
|
parser.add_argument("--dst-ip", default=None, help="destination ip address to use in the discovery")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("Discovering...")
|
||||||
|
devices = broadlink.discover(timeout=args.timeout, local_ip_address=args.ip, discover_ip_address=args.dst_ip)
|
||||||
|
for device in devices:
|
||||||
|
if device.auth():
|
||||||
|
print("###########################################")
|
||||||
|
print(device.type)
|
||||||
|
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("{} {} {}".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("")
|
||||||
|
else:
|
||||||
|
print("Error authenticating with device : {}".format(device.host))
|
202
third_party/python/broadlink/protocol.md
vendored
Normal file
202
third_party/python/broadlink/protocol.md
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Broadlink RM2 network protocol
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Encryption
|
||||||
|
----------
|
||||||
|
|
||||||
|
Packets include AES-based encryption in CBC mode. The initial key is 0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02. The IV is 0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58.
|
||||||
|
|
||||||
|
Checksum
|
||||||
|
--------
|
||||||
|
|
||||||
|
Construct the packet and set checksum bytes to zero. Add each byte to the starting value of 0xbeaf, wrapping after 0xffff.
|
||||||
|
|
||||||
|
New device setup
|
||||||
|
----------------
|
||||||
|
|
||||||
|
To setup a new Broadlink device while in AP Mode a 136 byte packet needs to be sent to the device as follows:
|
||||||
|
|
||||||
|
| Offset | Contents |
|
||||||
|
|---------|----------|
|
||||||
|
|0x00-0x19|00|
|
||||||
|
|0x20-0x21|Checksum as a little-endian 16 bit integer|
|
||||||
|
|0x26|14 (Always 14)|
|
||||||
|
|0x44-0x63|SSID Name (zero padding is appended)|
|
||||||
|
|0x64-0x83|Password (zero padding is appended)|
|
||||||
|
|0x84|Character length of SSID|
|
||||||
|
|0x85|Character length of password|
|
||||||
|
|0x86|Wireless security mode (00 - none, 01 = WEP, 02 = WPA1, 03 = WPA2, 04 = WPA1/2)|
|
||||||
|
|0x87-88|00|
|
||||||
|
|
||||||
|
Send this packet as a UDP broadcast to 255.255.255.255 on port 80.
|
||||||
|
|
||||||
|
Network discovery
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
To discover Broadlink devices on the local network, send a 48 byte packet with the following contents:
|
||||||
|
|
||||||
|
| Offset | Contents |
|
||||||
|
|---------|----------|
|
||||||
|
|0x00-0x07|00|
|
||||||
|
|0x08-0x0b|Current offset from GMT as a little-endian 32 bit integer|
|
||||||
|
|0x0c-0x0d|Current year as a little-endian 16 bit integer|
|
||||||
|
|0x0e|Current number of seconds past the minute|
|
||||||
|
|0x0f|Current number of minutes past the hour|
|
||||||
|
|0x10|Current number of hours past midnight|
|
||||||
|
|0x11|Current day of the week (Monday = 1, Tuesday = 2, etc)|
|
||||||
|
|0x12|Current day in month|
|
||||||
|
|0x13|Current month|
|
||||||
|
|0x14-0x17|00|
|
||||||
|
|0x18-0x1b|Local IP address|
|
||||||
|
|0x1c-0x1d|Source port as a little-endian 16 bit integer|
|
||||||
|
|0x1e-0x1f|00|
|
||||||
|
|0x20-0x21|Checksum as a little-endian 16 bit integer|
|
||||||
|
|0x22-0x25|00|
|
||||||
|
|0x26|06|
|
||||||
|
|0x27-0x2f|00|
|
||||||
|
|
||||||
|
Send this packet as a UDP broadcast to 255.255.255.255 on port 80.
|
||||||
|
|
||||||
|
Response (any unicast response):
|
||||||
|
|
||||||
|
| Offset | Contents |
|
||||||
|
|---------|----------|
|
||||||
|
|0x34-0x35|Device type as a little-endian 16 bit integer (see device type mapping)|
|
||||||
|
|0x3a-0x3f|MAC address of the target device|
|
||||||
|
|
||||||
|
Device type mapping:
|
||||||
|
|
||||||
|
| Device type in response packet | Device type | Treat as |
|
||||||
|
|---------|----------|----------|
|
||||||
|
|0|SP1|SP1|
|
||||||
|
|0x2711|SP2|SP2|
|
||||||
|
|0x2719 or 0x7919 or 0x271a or 0x791a|Honeywell SP2|SP2|
|
||||||
|
|0x2720|SPMini|SP2|
|
||||||
|
|0x753e|SP3|SP2|
|
||||||
|
|0x2728|SPMini2|SP2
|
||||||
|
|0x2733 or 0x273e|OEM branded SPMini|SP2|
|
||||||
|
|>= 0x7530 and <= 0x7918|OEM branded SPMini2|SP2|
|
||||||
|
|0x2736|SPMiniPlus|SP2|
|
||||||
|
|0x2712|RM2|RM|
|
||||||
|
|0x2737|RM Mini / RM3 Mini Blackbean|RM|
|
||||||
|
|0x273d|RM Pro Phicomm|RM|
|
||||||
|
|0x2783|RM2 Home Plus|RM|
|
||||||
|
|0x277c|RM2 Home Plus GDT|RM|
|
||||||
|
|0x272a|RM2 Pro Plus|RM|
|
||||||
|
|0x2787|RM2 Pro Plus2|RM|
|
||||||
|
|0x278b|RM2 Pro Plus BL|RM|
|
||||||
|
|0x278f|RM Mini Shate|RM|
|
||||||
|
|0x2714|A1|A1|
|
||||||
|
|0x4EB5|MP1|MP1|
|
||||||
|
|
||||||
|
|
||||||
|
Command packet format
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The command packet header is 56 bytes long with the following format:
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00|0x5a|
|
||||||
|
|0x01|0xa5|
|
||||||
|
|0x02|0xaa|
|
||||||
|
|0x03|0x55|
|
||||||
|
|0x04|0x5a|
|
||||||
|
|0x05|0xa5|
|
||||||
|
|0x06|0xaa|
|
||||||
|
|0x07|0x55|
|
||||||
|
|0x08-0x1f|00|
|
||||||
|
|0x20-0x21|Checksum of full packet as a little-endian 16 bit integer|
|
||||||
|
|0x22-0x23|00|
|
||||||
|
|0x24-0x25|Device type as a little-endian 16 bit integer|
|
||||||
|
|0x26-0x27|Command code as a little-endian 16 bit integer|
|
||||||
|
|0x28-0x29|Packet count as a little-endian 16 bit integer|
|
||||||
|
|0x2a-0x2f|Local MAC address|
|
||||||
|
|0x30-0x33|Local device ID (obtained during authentication, 00 before authentication)|
|
||||||
|
|0x34-0x35|Checksum of unencrypted payload as a little-endian 16 bit integer
|
||||||
|
|0x36-0x37|00|
|
||||||
|
|
||||||
|
The payload is appended immediately after this. The checksum at 0x20 is calculated *after* the payload is appended, and covers the entire packet (including the checksum at 0x34). Therefore:
|
||||||
|
|
||||||
|
1. Generate packet header with checksum values set to 0
|
||||||
|
2. Set the checksum initialisation value to 0xbeaf and calculate the checksum of the unencrypted payload. Set 0x34-0x35 to this value.
|
||||||
|
3. Encrypt and append the payload
|
||||||
|
4. Set the checksum initialisation value to 0xbeaf and calculate the checksum of the entire packet. Set 0x20-0x21 to this value.
|
||||||
|
|
||||||
|
Authorisation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You must obtain an authorisation key from the device before you can communicate. To do so, generate an 80 byte packet with the following contents:
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00-0x03|00|
|
||||||
|
|0x04-0x12|A 15-digit value that represents this device. Broadlink's implementation uses the IMEI.|
|
||||||
|
|0x13|01|
|
||||||
|
|0x14-0x2c|00|
|
||||||
|
|0x2d|0x01|
|
||||||
|
|0x30-0x7f|NULL-terminated ASCII string containing the device name|
|
||||||
|
|
||||||
|
Send this payload with a command value of 0x0065. The response packet will contain an encrypted payload from byte 0x38 onwards. Decrypt this using the default key and IV. The format of the decrypted payload is:
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00-0x03|Device ID|
|
||||||
|
|0x04-0x13|Device encryption key|
|
||||||
|
|
||||||
|
All further command packets must use this encryption key and device ID.
|
||||||
|
|
||||||
|
Entering learning mode
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Send the following 16 byte payload with a command value of 0x006a:
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00|0x03|
|
||||||
|
|0x01-0x0f|0x00|
|
||||||
|
|
||||||
|
Reading back data from learning mode
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Send the following 16 byte payload with a command value of 0x006a:
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00|0x04|
|
||||||
|
|0x01-0x0f|0x00|
|
||||||
|
|
||||||
|
Byte 0x22 of the response contains a little-endian 16 bit error code. If this is 0, a code has been obtained. Bytes 0x38 and onward of the response are encrypted. Decrypt them. Bytes 0x04 and onward of the decrypted payload contain the captured data.
|
||||||
|
|
||||||
|
Sending data
|
||||||
|
------------
|
||||||
|
|
||||||
|
Send the following payload with a command byte of 0x006a
|
||||||
|
|
||||||
|
|Offset|Contents|
|
||||||
|
|------|--------|
|
||||||
|
|0x00|0x02|
|
||||||
|
|0x01-0x03|0x00|
|
||||||
|
|0x04|0x26 = IR, 0xb2 for RF 433Mhz, 0xd7 for RF 315Mhz|
|
||||||
|
|0x05|repeat count, (0 = no repeat, 1 send twice, .....)|
|
||||||
|
|0x06-0x07|Length of the following data in little endian|
|
||||||
|
|0x08 ....|Pulse lengths in 2^-15 s units (µs * 269 / 8192 works very well)|
|
||||||
|
|....|0x0d 0x05 at the end for IR only|
|
||||||
|
|
||||||
|
Each value is represented by one byte. If the length exceeds one byte
|
||||||
|
then it is stored big endian with a leading 0.
|
||||||
|
|
||||||
|
Example: The header for my Optoma projector is 8920 4450
|
||||||
|
8920 * 269 / 8192 = 0x124
|
||||||
|
4450 * 269 / 8192 = 0x92
|
||||||
|
|
||||||
|
So the data starts with `0x00 0x1 0x24 0x92 ....`
|
||||||
|
|
||||||
|
|
||||||
|
Todo
|
||||||
|
----
|
||||||
|
|
||||||
|
* Support for other devices using the Broadlink protocol (various smart home devices)
|
||||||
|
* Figure out what the format of the data packets actually is.
|
||||||
|
* Deal with the response after AP Mode WiFi network setup.
|
||||||
|
|
1
third_party/python/broadlink/requirements.txt
vendored
Normal file
1
third_party/python/broadlink/requirements.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cryptography==2.6.1
|
29
third_party/python/broadlink/setup.py
vendored
Normal file
29
third_party/python/broadlink/setup.py
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
version = '0.13.2'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='broadlink',
|
||||||
|
version=version,
|
||||||
|
author='Matthew Garrett',
|
||||||
|
author_email='mjg59@srcf.ucam.org',
|
||||||
|
url='http://github.com/mjg59/python-broadlink',
|
||||||
|
packages=find_packages(),
|
||||||
|
scripts=[],
|
||||||
|
install_requires=['cryptography>=2.1.1'],
|
||||||
|
description='Python API for controlling Broadlink IR controllers',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
],
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
Loading…
Reference in a new issue