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:
Vincent Ambo 2020-04-25 18:22:59 +01:00
commit 9d94a727b2
10 changed files with 1836 additions and 0 deletions

View file

@ -0,0 +1 @@
*.pyc

22
third_party/python/broadlink/LICENSE vendored Normal file
View 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
View 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()
```

File diff suppressed because it is too large Load diff

View 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
View 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...")

View 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
View 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.

View file

@ -0,0 +1 @@
cryptography==2.6.1

29
third_party/python/broadlink/setup.py vendored Normal file
View 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,
)