2020-10-21 19:32:32 +02:00
|
|
|
#!/usr/bin/env python3
|
2020-09-16 09:41:28 +02:00
|
|
|
"""The python-broadlink library."""
|
2019-05-27 20:57:32 +02:00
|
|
|
import socket
|
2021-03-31 19:27:05 +02:00
|
|
|
import typing as t
|
2019-05-19 17:54:14 +02:00
|
|
|
|
2021-03-31 19:27:05 +02:00
|
|
|
from . import exceptions as e
|
2021-04-29 23:59:58 +02:00
|
|
|
from .const import DEFAULT_BCAST_ADDR, DEFAULT_PORT, DEFAULT_TIMEOUT
|
2020-09-17 05:41:32 +02:00
|
|
|
from .alarm import S1C
|
|
|
|
from .climate import hysen
|
2020-09-20 06:34:31 +02:00
|
|
|
from .cover import dooya
|
2021-04-03 06:01:38 +02:00
|
|
|
from .device import Device, ping, scan
|
2021-04-30 01:31:30 +02:00
|
|
|
from .light import lb1, lb2
|
2021-02-16 22:14:11 +01:00
|
|
|
from .remote import rm, rm4, rm4mini, rm4pro, rmmini, rmminib, rmpro
|
2020-09-17 05:41:32 +02:00
|
|
|
from .sensor import a1
|
2021-02-16 20:38:10 +01:00
|
|
|
from .switch import bg1, mp1, sp1, sp2, sp2s, sp3, sp3s, sp4, sp4b
|
2020-06-16 21:19:32 +02:00
|
|
|
|
2020-09-24 07:36:12 +02:00
|
|
|
SUPPORTED_TYPES = {
|
2021-05-06 19:52:11 +02:00
|
|
|
sp1: {
|
|
|
|
0x0000: ("SP1", "Broadlink"),
|
|
|
|
},
|
|
|
|
sp2: {
|
|
|
|
0x2717: ("NEO", "Ankuoo"),
|
|
|
|
0x2719: ("SP2-compatible", "Honeywell"),
|
|
|
|
0x271A: ("SP2-compatible", "Honeywell"),
|
|
|
|
0x2720: ("SP mini", "Broadlink"),
|
|
|
|
0x2728: ("SP2-compatible", "URANT"),
|
|
|
|
0x273E: ("SP mini", "Broadlink"),
|
|
|
|
0x7530: ("SP2", "Broadlink (OEM)"),
|
|
|
|
0x7539: ("SP2-IL", "Broadlink (OEM)"),
|
|
|
|
0x753E: ("SP mini 3", "Broadlink"),
|
|
|
|
0x7540: ("MP2", "Broadlink"),
|
|
|
|
0x7544: ("SP2-CL", "Broadlink"),
|
|
|
|
0x7546: ("SP2-UK/BR/IN", "Broadlink (OEM)"),
|
|
|
|
0x7547: ("SC1", "Broadlink"),
|
|
|
|
0x7918: ("SP2", "Broadlink (OEM)"),
|
|
|
|
0x7919: ("SP2-compatible", "Honeywell"),
|
|
|
|
0x791A: ("SP2-compatible", "Honeywell"),
|
|
|
|
0x7D0D: ("SP mini 3", "Broadlink (OEM)"),
|
|
|
|
},
|
|
|
|
sp2s: {
|
|
|
|
0x2711: ("SP2", "Broadlink"),
|
|
|
|
0x2716: ("NEO PRO", "Ankuoo"),
|
|
|
|
0x271D: ("Ego", "Efergy"),
|
|
|
|
0x2736: ("SP mini+", "Broadlink"),
|
|
|
|
},
|
|
|
|
sp3: {
|
|
|
|
0x2733: ("SP3", "Broadlink"),
|
|
|
|
0x7D00: ("SP3-EU", "Broadlink (OEM)"),
|
|
|
|
},
|
|
|
|
sp3s: {
|
|
|
|
0x9479: ("SP3S-US", "Broadlink"),
|
|
|
|
0x947A: ("SP3S-EU", "Broadlink"),
|
|
|
|
},
|
|
|
|
sp4: {
|
|
|
|
0x7568: ("SP4L-CN", "Broadlink"),
|
|
|
|
0x756C: ("SP4M", "Broadlink"),
|
|
|
|
0x756F: ("MCB1", "Broadlink"),
|
|
|
|
0x7579: ("SP4L-EU", "Broadlink"),
|
|
|
|
0x757B: ("SP4L-AU", "Broadlink"),
|
|
|
|
0x7583: ("SP mini 3", "Broadlink"),
|
|
|
|
0x7587: ("SP4L-UK", "Broadlink"),
|
|
|
|
0x7D11: ("SP mini 3", "Broadlink"),
|
|
|
|
0xA56A: ("MCB1", "Broadlink"),
|
|
|
|
0xA589: ("SP4L-UK", "Broadlink"),
|
|
|
|
0xA5D3: ("SP4L-EU", "Broadlink"),
|
|
|
|
},
|
|
|
|
sp4b: {
|
|
|
|
0x5115: ("SCB1E", "Broadlink"),
|
|
|
|
0x51E2: ("AHC/U-01", "BG Electrical"),
|
|
|
|
0x6111: ("MCB1", "Broadlink"),
|
|
|
|
0x6113: ("SCB1E", "Broadlink"),
|
|
|
|
0x618B: ("SP4L-EU", "Broadlink"),
|
|
|
|
0x6489: ("SP4L-AU", "Broadlink"),
|
|
|
|
0x648B: ("SP4M-US", "Broadlink"),
|
|
|
|
0x6494: ("SCB2", "Broadlink"),
|
|
|
|
},
|
|
|
|
rmmini: {
|
|
|
|
0x2737: ("RM mini 3", "Broadlink"),
|
|
|
|
0x278F: ("RM mini", "Broadlink"),
|
|
|
|
0x27C2: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27C7: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27CC: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27CD: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27D0: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27D1: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27D3: ("RM mini 3", "Broadlink"),
|
|
|
|
0x27DE: ("RM mini 3", "Broadlink"),
|
|
|
|
},
|
|
|
|
rmpro: {
|
|
|
|
0x2712: ("RM pro/pro+", "Broadlink"),
|
|
|
|
0x272A: ("RM pro", "Broadlink"),
|
|
|
|
0x273D: ("RM pro", "Broadlink"),
|
|
|
|
0x277C: ("RM home", "Broadlink"),
|
|
|
|
0x2783: ("RM home", "Broadlink"),
|
|
|
|
0x2787: ("RM pro", "Broadlink"),
|
|
|
|
0x278B: ("RM plus", "Broadlink"),
|
|
|
|
0x2797: ("RM pro+", "Broadlink"),
|
|
|
|
0x279D: ("RM pro+", "Broadlink"),
|
|
|
|
0x27A1: ("RM plus", "Broadlink"),
|
|
|
|
0x27A6: ("RM plus", "Broadlink"),
|
|
|
|
0x27A9: ("RM pro+", "Broadlink"),
|
|
|
|
0x27C3: ("RM pro+", "Broadlink"),
|
|
|
|
},
|
|
|
|
rmminib: {
|
|
|
|
0x5F36: ("RM mini 3", "Broadlink"),
|
|
|
|
0x6507: ("RM mini 3", "Broadlink"),
|
|
|
|
0x6508: ("RM mini 3", "Broadlink"),
|
|
|
|
},
|
|
|
|
rm4mini: {
|
|
|
|
0x51DA: ("RM4 mini", "Broadlink"),
|
|
|
|
0x6070: ("RM4C mini", "Broadlink"),
|
|
|
|
0x610E: ("RM4 mini", "Broadlink"),
|
|
|
|
0x610F: ("RM4C mini", "Broadlink"),
|
|
|
|
0x62BC: ("RM4 mini", "Broadlink"),
|
|
|
|
0x62BE: ("RM4C mini", "Broadlink"),
|
|
|
|
0x6364: ("RM4S", "Broadlink"),
|
|
|
|
0x648D: ("RM4 mini", "Broadlink"),
|
|
|
|
0x6539: ("RM4C mini", "Broadlink"),
|
|
|
|
0x653A: ("RM4 mini", "Broadlink"),
|
|
|
|
},
|
|
|
|
rm4pro: {
|
|
|
|
0x6026: ("RM4 pro", "Broadlink"),
|
|
|
|
0x6184: ("RM4C pro", "Broadlink"),
|
|
|
|
0x61A2: ("RM4 pro", "Broadlink"),
|
|
|
|
0x649B: ("RM4 pro", "Broadlink"),
|
|
|
|
0x653C: ("RM4 pro", "Broadlink"),
|
|
|
|
},
|
|
|
|
a1: {
|
|
|
|
0x2714: ("e-Sensor", "Broadlink"),
|
|
|
|
},
|
|
|
|
mp1: {
|
|
|
|
0x4EB5: ("MP1-1K4S", "Broadlink"),
|
|
|
|
0x4EF7: ("MP1-1K4S", "Broadlink (OEM)"),
|
|
|
|
0x4F1B: ("MP1-1K3S2U", "Broadlink (OEM)"),
|
|
|
|
0x4F65: ("MP1-1K3S2U", "Broadlink"),
|
|
|
|
},
|
|
|
|
lb1: {
|
|
|
|
0x5043: ("SB800TD", "Broadlink (OEM)"),
|
|
|
|
0x504E: ("LB1", "Broadlink"),
|
|
|
|
0x60C7: ("LB1", "Broadlink"),
|
|
|
|
0x60C8: ("LB1", "Broadlink"),
|
|
|
|
0x6112: ("LB1", "Broadlink"),
|
|
|
|
},
|
|
|
|
lb2: {
|
|
|
|
0xA4F4: ("LB27 R1", "Broadlink"),
|
|
|
|
},
|
|
|
|
S1C: {
|
|
|
|
0x2722: ("S2KIT", "Broadlink"),
|
|
|
|
},
|
|
|
|
hysen: {
|
|
|
|
0x4EAD: ("HY02B05H", "Hysen"),
|
|
|
|
},
|
|
|
|
dooya: {
|
|
|
|
0x4E4D: ("DT360E-45/20", "Dooya"),
|
|
|
|
},
|
|
|
|
bg1: {
|
|
|
|
0x51E3: ("BG800/BG900", "BG Electrical"),
|
|
|
|
},
|
2020-09-24 07:36:12 +02:00
|
|
|
}
|
2019-05-19 17:54:14 +02:00
|
|
|
|
2020-07-19 02:53:00 +02:00
|
|
|
|
2020-09-17 02:35:09 +02:00
|
|
|
def gendevice(
|
2020-11-05 20:00:42 +01:00
|
|
|
dev_type: int,
|
2021-03-31 19:27:05 +02:00
|
|
|
host: t.Tuple[str, int],
|
|
|
|
mac: t.Union[bytes, str],
|
2021-04-05 19:02:56 +02:00
|
|
|
name: str = "",
|
|
|
|
is_locked: bool = False,
|
2021-04-03 06:01:38 +02:00
|
|
|
) -> Device:
|
2020-07-19 02:53:00 +02:00
|
|
|
"""Generate a device."""
|
2021-05-06 19:52:11 +02:00
|
|
|
for dev_cls, products in SUPPORTED_TYPES.items():
|
|
|
|
try:
|
|
|
|
model, manufacturer = products[dev_type]
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
continue
|
2020-07-19 02:53:00 +02:00
|
|
|
|
2021-05-06 19:52:11 +02:00
|
|
|
return dev_cls(
|
|
|
|
host,
|
|
|
|
mac,
|
|
|
|
dev_type,
|
|
|
|
name=name,
|
|
|
|
model=model,
|
|
|
|
manufacturer=manufacturer,
|
|
|
|
is_locked=is_locked,
|
|
|
|
)
|
2020-06-16 21:19:32 +02:00
|
|
|
|
2021-05-06 19:52:11 +02:00
|
|
|
return Device(host, mac, dev_type, name=name, is_locked=is_locked)
|
2019-05-19 17:54:14 +02:00
|
|
|
|
2016-11-19 23:22:08 +01:00
|
|
|
|
2020-09-23 07:43:56 +02:00
|
|
|
def hello(
|
2020-11-05 20:00:42 +01:00
|
|
|
host: str,
|
2021-04-29 23:59:58 +02:00
|
|
|
port: int = DEFAULT_PORT,
|
|
|
|
timeout: int = DEFAULT_TIMEOUT,
|
2021-04-03 06:01:38 +02:00
|
|
|
) -> Device:
|
2020-09-23 07:43:56 +02:00
|
|
|
"""Direct device discovery.
|
|
|
|
|
|
|
|
Useful if the device is locked.
|
|
|
|
"""
|
|
|
|
try:
|
2021-05-03 22:34:22 +02:00
|
|
|
return next(
|
|
|
|
xdiscover(timeout=timeout, discover_ip_address=host, discover_ip_port=port)
|
|
|
|
)
|
2021-04-02 18:21:30 +02:00
|
|
|
except StopIteration as err:
|
|
|
|
raise e.NetworkTimeoutError(
|
|
|
|
-4000,
|
|
|
|
"Network timeout",
|
|
|
|
f"No response received within {timeout}s",
|
|
|
|
) from err
|
2020-09-23 07:43:56 +02:00
|
|
|
|
|
|
|
|
2020-08-10 21:28:09 +02:00
|
|
|
def discover(
|
2021-04-29 23:59:58 +02:00
|
|
|
timeout: int = DEFAULT_TIMEOUT,
|
2020-11-05 20:00:42 +01:00
|
|
|
local_ip_address: str = None,
|
2021-04-29 23:59:58 +02:00
|
|
|
discover_ip_address: str = DEFAULT_BCAST_ADDR,
|
|
|
|
discover_ip_port: int = DEFAULT_PORT,
|
2021-04-03 06:01:38 +02:00
|
|
|
) -> t.List[Device]:
|
2020-09-16 09:41:28 +02:00
|
|
|
"""Discover devices connected to the local network."""
|
2020-11-05 20:00:42 +01:00
|
|
|
responses = scan(timeout, local_ip_address, discover_ip_address, discover_ip_port)
|
2020-09-23 07:43:56 +02:00
|
|
|
return [gendevice(*resp) for resp in responses]
|
|
|
|
|
|
|
|
|
|
|
|
def xdiscover(
|
2021-04-29 23:59:58 +02:00
|
|
|
timeout: int = DEFAULT_TIMEOUT,
|
2020-11-05 20:00:42 +01:00
|
|
|
local_ip_address: str = None,
|
2021-04-29 23:59:58 +02:00
|
|
|
discover_ip_address: str = DEFAULT_BCAST_ADDR,
|
|
|
|
discover_ip_port: int = DEFAULT_PORT,
|
2021-04-03 06:01:38 +02:00
|
|
|
) -> t.Generator[Device, None, None]:
|
2020-09-23 07:43:56 +02:00
|
|
|
"""Discover devices connected to the local network.
|
|
|
|
|
|
|
|
This function returns a generator that yields devices instantly.
|
|
|
|
"""
|
2020-11-05 20:00:42 +01:00
|
|
|
responses = scan(timeout, local_ip_address, discover_ip_address, discover_ip_port)
|
2020-09-23 07:43:56 +02:00
|
|
|
for resp in responses:
|
|
|
|
yield gendevice(*resp)
|
2019-05-19 17:54:14 +02:00
|
|
|
|
|
|
|
|
2017-04-22 21:48:02 +02:00
|
|
|
# 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)
|
2020-09-17 02:35:09 +02:00
|
|
|
def setup(ssid: str, password: str, security_mode: int) -> None:
|
2020-09-16 09:41:28 +02:00
|
|
|
"""Set up a new Broadlink device via AP mode."""
|
2019-05-19 17:54:14 +02:00
|
|
|
# Security mode options are (0 - none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
|
|
|
|
payload = bytearray(0x88)
|
|
|
|
payload[0x26] = 0x14 # This seems to always be set to 14
|
|
|
|
# Add the SSID to the payload
|
|
|
|
ssid_start = 68
|
|
|
|
ssid_length = 0
|
|
|
|
for letter in ssid:
|
|
|
|
payload[(ssid_start + ssid_length)] = ord(letter)
|
|
|
|
ssid_length += 1
|
|
|
|
# Add the WiFi password to the payload
|
|
|
|
pass_start = 100
|
|
|
|
pass_length = 0
|
|
|
|
for letter in password:
|
|
|
|
payload[(pass_start + pass_length)] = ord(letter)
|
|
|
|
pass_length += 1
|
|
|
|
|
|
|
|
payload[0x84] = ssid_length # Character length of SSID
|
|
|
|
payload[0x85] = pass_length # Character length of password
|
2020-09-24 07:36:12 +02:00
|
|
|
payload[0x86] = security_mode # Type of encryption
|
2019-05-19 17:54:14 +02:00
|
|
|
|
2020-11-05 20:00:42 +01:00
|
|
|
checksum = sum(payload, 0xBEAF) & 0xFFFF
|
|
|
|
payload[0x20] = checksum & 0xFF # Checksum 1 position
|
2019-05-19 17:54:14 +02:00
|
|
|
payload[0x21] = checksum >> 8 # Checksum 2 position
|
2017-04-22 21:48:02 +02:00
|
|
|
|
2020-11-05 20:00:42 +01:00
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Internet # UDP
|
2019-05-19 17:54:14 +02:00
|
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
2021-04-29 23:59:58 +02:00
|
|
|
sock.sendto(payload, (DEFAULT_BCAST_ADDR, DEFAULT_PORT))
|
2020-04-25 11:40:48 +02:00
|
|
|
sock.close()
|