python-broadlink 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- broadlink/__init__.py +342 -0
- broadlink/alarm.py +43 -0
- broadlink/climate.py +474 -0
- broadlink/const.py +5 -0
- broadlink/cover.py +182 -0
- broadlink/device.py +470 -0
- broadlink/exceptions.py +161 -0
- broadlink/helpers.py +43 -0
- broadlink/hub.py +98 -0
- broadlink/light.py +200 -0
- broadlink/protocol.py +50 -0
- broadlink/remote.py +521 -0
- broadlink/sensor.py +90 -0
- broadlink/switch.py +472 -0
- python_broadlink-1.0.0.dist-info/METADATA +360 -0
- python_broadlink-1.0.0.dist-info/RECORD +19 -0
- python_broadlink-1.0.0.dist-info/WHEEL +5 -0
- python_broadlink-1.0.0.dist-info/licenses/LICENSE +22 -0
- python_broadlink-1.0.0.dist-info/top_level.txt +1 -0
broadlink/__init__.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The python-broadlink library."""
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from typing import List, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
from . import exceptions as e
|
|
7
|
+
from .alarm import S1C
|
|
8
|
+
from .climate import hvac, hysen
|
|
9
|
+
from .const import DEFAULT_BCAST_ADDR, DEFAULT_PORT, DEFAULT_TIMEOUT
|
|
10
|
+
from .cover import dooya, dooya2, wser
|
|
11
|
+
from .device import Device, _open_endpoint, ping, scan
|
|
12
|
+
from .hub import s3
|
|
13
|
+
from .light import lb1, lb2
|
|
14
|
+
from .remote import rm, rm4, rm4mini, rm4pro, rm5plus, rmmini, rmminib, rmpro
|
|
15
|
+
from .sensor import a1, a2
|
|
16
|
+
from .switch import bg1, ehc31, mp1, mp1s, sp1, sp2, sp2s, sp3, sp3s, sp4, sp4b
|
|
17
|
+
|
|
18
|
+
SUPPORTED_TYPES = {
|
|
19
|
+
sp1: {
|
|
20
|
+
0x0000: ("SP1", "Broadlink"),
|
|
21
|
+
},
|
|
22
|
+
sp2: {
|
|
23
|
+
0x2717: ("NEO", "Ankuoo"),
|
|
24
|
+
0x2719: ("SP2-compatible", "Honeywell"),
|
|
25
|
+
0x271A: ("SP2-compatible", "Honeywell"),
|
|
26
|
+
0x2720: ("SP mini", "Broadlink"),
|
|
27
|
+
0x2728: ("SP2-compatible", "URANT"),
|
|
28
|
+
0x273E: ("SP mini", "Broadlink"),
|
|
29
|
+
0x7530: ("SP2", "Broadlink (OEM)"),
|
|
30
|
+
0x7539: ("SP2-IL", "Broadlink (OEM)"),
|
|
31
|
+
0x753E: ("SP mini 3", "Broadlink"),
|
|
32
|
+
0x7540: ("MP2", "Broadlink"),
|
|
33
|
+
0x7544: ("SP2-CL", "Broadlink"),
|
|
34
|
+
0x7546: ("SP2-UK/BR/IN", "Broadlink (OEM)"),
|
|
35
|
+
0x7547: ("SC1", "Broadlink"),
|
|
36
|
+
0x7549: ("SP mini 3", "Broadlink (OEM)"),
|
|
37
|
+
0x7918: ("SP2", "Broadlink (OEM)"),
|
|
38
|
+
0x7919: ("SP2-compatible", "Honeywell"),
|
|
39
|
+
0x791A: ("SP2-compatible", "Honeywell"),
|
|
40
|
+
0x7D0D: ("SP mini 3", "Broadlink (OEM)"),
|
|
41
|
+
},
|
|
42
|
+
sp2s: {
|
|
43
|
+
0x2711: ("SP2", "Broadlink"),
|
|
44
|
+
0x2716: ("NEO PRO", "Ankuoo"),
|
|
45
|
+
0x271D: ("Ego", "Efergy"),
|
|
46
|
+
0x2736: ("SP mini+", "Broadlink"),
|
|
47
|
+
},
|
|
48
|
+
sp3: {
|
|
49
|
+
0x2733: ("SP3", "Broadlink"),
|
|
50
|
+
0x7D00: ("SP3-EU", "Broadlink (OEM)"),
|
|
51
|
+
},
|
|
52
|
+
sp3s: {
|
|
53
|
+
0x9479: ("SP3S-US", "Broadlink"),
|
|
54
|
+
0x947A: ("SP3S-EU", "Broadlink"),
|
|
55
|
+
},
|
|
56
|
+
sp4: {
|
|
57
|
+
0x7568: ("SP4L-CN", "Broadlink"),
|
|
58
|
+
0x756B: ("SP4M-JP", "Broadlink"),
|
|
59
|
+
0x756C: ("SP4M", "Broadlink"),
|
|
60
|
+
0x756F: ("MCB1", "Broadlink"),
|
|
61
|
+
0x7579: ("SP4L-EU", "Broadlink"),
|
|
62
|
+
0x757B: ("SP4L-AU", "Broadlink"),
|
|
63
|
+
0x7583: ("SP mini 3", "Broadlink"),
|
|
64
|
+
0x7587: ("SP4L-UK", "Broadlink"),
|
|
65
|
+
0x7D11: ("SP mini 3", "Broadlink"),
|
|
66
|
+
0x7D15: ("SP mini 3-AL", "Broadlink (OEM)"),
|
|
67
|
+
0xA4F9: ("WS4", "Broadlink (OEM)"),
|
|
68
|
+
0xA569: ("SP4L-UK", "Broadlink"),
|
|
69
|
+
0xA56A: ("MCB1", "Broadlink"),
|
|
70
|
+
0xA56B: ("SCB1E", "Broadlink"),
|
|
71
|
+
0xA56C: ("SP4L-EU", "Broadlink"),
|
|
72
|
+
0xA576: ("SP4L-AU", "Broadlink"),
|
|
73
|
+
0xA589: ("SP4L-UK", "Broadlink"),
|
|
74
|
+
0xA5D3: ("SP4L-EU", "Broadlink"),
|
|
75
|
+
0xA57A: ("SP4", "Broadlink"),
|
|
76
|
+
0xA6F4: ("SP4D-US", "Broadlink"),
|
|
77
|
+
},
|
|
78
|
+
sp4b: {
|
|
79
|
+
0x5115: ("SCB1E", "Broadlink"),
|
|
80
|
+
0x51E2: ("AHC/U-01", "BG Electrical"),
|
|
81
|
+
0x6111: ("MCB1", "Broadlink"),
|
|
82
|
+
0x6113: ("SCB1E", "Broadlink"),
|
|
83
|
+
0x618B: ("SP4L-EU", "Broadlink"),
|
|
84
|
+
0x6489: ("SP4L-AU", "Broadlink"),
|
|
85
|
+
0x648B: ("SP4M-US", "Broadlink"),
|
|
86
|
+
0x648C: ("SP4L-US", "Broadlink"),
|
|
87
|
+
0x6494: ("SCB2", "Broadlink"),
|
|
88
|
+
},
|
|
89
|
+
rmmini: {
|
|
90
|
+
0x2737: ("RM mini 3", "Broadlink"),
|
|
91
|
+
0x278F: ("RM mini", "Broadlink"),
|
|
92
|
+
0x27B7: ("RM mini 3", "Broadlink"),
|
|
93
|
+
0x27C2: ("RM mini 3", "Broadlink"),
|
|
94
|
+
0x27C7: ("RM mini 3", "Broadlink"),
|
|
95
|
+
0x27C8: ("RM mini 3", "Broadlink"), # CMCC version
|
|
96
|
+
0x27CC: ("RM mini 3", "Broadlink"),
|
|
97
|
+
0x27CD: ("RM mini 3", "Broadlink"),
|
|
98
|
+
0x27D0: ("RM mini 3", "Broadlink"),
|
|
99
|
+
0x27D1: ("RM mini 3", "Broadlink"),
|
|
100
|
+
0x27D3: ("RM mini 3", "Broadlink"),
|
|
101
|
+
0x27DC: ("RM mini 3", "Broadlink"),
|
|
102
|
+
0x27DE: ("RM mini 3", "Broadlink"),
|
|
103
|
+
0xA544: ("RM mini 3", "Broadlink (OEM)"),
|
|
104
|
+
},
|
|
105
|
+
rmpro: {
|
|
106
|
+
0x2712: ("RM pro/pro+", "Broadlink"),
|
|
107
|
+
0x272A: ("RM pro", "Broadlink"),
|
|
108
|
+
0x273D: ("RM pro", "Broadlink"),
|
|
109
|
+
0x277C: ("RM home", "Broadlink"),
|
|
110
|
+
0x2783: ("RM home", "Broadlink"),
|
|
111
|
+
0x2787: ("RM pro", "Broadlink"),
|
|
112
|
+
0x278B: ("RM plus", "Broadlink"),
|
|
113
|
+
0x2797: ("RM pro+", "Broadlink"),
|
|
114
|
+
0x279D: ("RM pro+", "Broadlink"),
|
|
115
|
+
0x27A1: ("RM plus", "Broadlink"),
|
|
116
|
+
0x27A6: ("RM plus", "Broadlink"),
|
|
117
|
+
0x27A9: ("RM pro+", "Broadlink"),
|
|
118
|
+
0x27C3: ("RM pro+", "Broadlink"),
|
|
119
|
+
0xAF8B: ("RM Max", "Broadlink"),
|
|
120
|
+
},
|
|
121
|
+
rmminib: {
|
|
122
|
+
0x5F36: ("RM mini 3", "Broadlink"),
|
|
123
|
+
0x6507: ("RM mini 3", "Broadlink"),
|
|
124
|
+
0x6508: ("RM mini 3", "Broadlink"),
|
|
125
|
+
},
|
|
126
|
+
rm4mini: {
|
|
127
|
+
0x51DA: ("RM4 mini", "Broadlink"),
|
|
128
|
+
0x5209: ("RM4 TV mate", "Broadlink"),
|
|
129
|
+
0x520C: ("RM4 mini", "Broadlink"),
|
|
130
|
+
0x520D: ("RM4C mini", "Broadlink"),
|
|
131
|
+
0x5211: ("RM4C mate", "Broadlink"),
|
|
132
|
+
0x5212: ("RM4 TV mate", "Broadlink"),
|
|
133
|
+
0x5216: ("RM4 mini", "Broadlink"),
|
|
134
|
+
0x521C: ("RM4 mini", "Broadlink"),
|
|
135
|
+
0x6070: ("RM4C mini", "Broadlink"),
|
|
136
|
+
0x610E: ("RM4 mini", "Broadlink"),
|
|
137
|
+
0x610F: ("RM4C mini", "Broadlink"),
|
|
138
|
+
0x62BC: ("RM4 mini", "Broadlink"),
|
|
139
|
+
0x62BE: ("RM4C mini", "Broadlink"),
|
|
140
|
+
0x6364: ("RM4S", "Broadlink"),
|
|
141
|
+
0x648D: ("RM4 mini", "Broadlink"),
|
|
142
|
+
0x6539: ("RM4C mini", "Broadlink"),
|
|
143
|
+
0x653A: ("RM4 mini", "Broadlink"),
|
|
144
|
+
},
|
|
145
|
+
rm4pro: {
|
|
146
|
+
0x520B: ("RM4 pro", "Broadlink"),
|
|
147
|
+
0x5213: ("RM4 pro", "Broadlink"),
|
|
148
|
+
0x5218: ("RM4C pro", "Broadlink"),
|
|
149
|
+
0x6026: ("RM4 pro", "Broadlink"),
|
|
150
|
+
0x6184: ("RM4C pro", "Broadlink"),
|
|
151
|
+
0x61A2: ("RM4 pro", "Broadlink"),
|
|
152
|
+
0x649B: ("RM4 pro", "Broadlink"),
|
|
153
|
+
0x653C: ("RM4 pro", "Broadlink"),
|
|
154
|
+
},
|
|
155
|
+
rm5plus: {
|
|
156
|
+
0x5224: ("RM5 plus", "Broadlink"),
|
|
157
|
+
},
|
|
158
|
+
a1: {
|
|
159
|
+
0x2714: ("A1", "Broadlink"),
|
|
160
|
+
},
|
|
161
|
+
a2: {
|
|
162
|
+
0x4F60: ("A2", "Broadlink"),
|
|
163
|
+
},
|
|
164
|
+
mp1: {
|
|
165
|
+
0x4EB5: ("MP1-1K4S", "Broadlink"),
|
|
166
|
+
0x4EDA: ("MP1-1K3S2U", "Broadlink"),
|
|
167
|
+
0x4F1B: ("MP1-1K3S2U", "Broadlink (OEM)"),
|
|
168
|
+
0x4F65: ("MP1-1K3S2U", "Broadlink"),
|
|
169
|
+
},
|
|
170
|
+
mp1s: {
|
|
171
|
+
0x4EF7: ("MP1-1K4S", "Broadlink (OEM)"),
|
|
172
|
+
},
|
|
173
|
+
lb1: {
|
|
174
|
+
0x5043: ("SB800TD", "Broadlink (OEM)"),
|
|
175
|
+
0x504E: ("LB1", "Broadlink"),
|
|
176
|
+
0x606D: ("SLA22RGB9W81/SLA27RGB9W81", "Luceco"),
|
|
177
|
+
0x606E: ("SB500TD", "Broadlink (OEM)"),
|
|
178
|
+
0x60C7: ("LB1", "Broadlink"),
|
|
179
|
+
0x60C8: ("LB1", "Broadlink"),
|
|
180
|
+
0x6112: ("LB1", "Broadlink"),
|
|
181
|
+
0x644B: ("LB1", "Broadlink"),
|
|
182
|
+
0x644C: ("LB27 R1", "Broadlink"),
|
|
183
|
+
0x644E: ("LB26 R1", "Broadlink"),
|
|
184
|
+
0x6488: ("LB27 C1", "Broadlink"),
|
|
185
|
+
0x6498: ("SMART+ WIFI CEILING TW 24W", "LEDVANCE"),
|
|
186
|
+
},
|
|
187
|
+
lb2: {
|
|
188
|
+
0xA4F4: ("LB27 R1", "Broadlink"),
|
|
189
|
+
0xA5F7: ("LB27 R1", "Broadlink"),
|
|
190
|
+
0xA6EF: ("EFCF60WSMT", "Luceco"),
|
|
191
|
+
0xA517: ("LB26 R1", "Broadlink"),
|
|
192
|
+
},
|
|
193
|
+
S1C: {
|
|
194
|
+
0x2722: ("S2KIT", "Broadlink"),
|
|
195
|
+
},
|
|
196
|
+
s3: {
|
|
197
|
+
0xA59C: ("S3", "Broadlink"),
|
|
198
|
+
0xA64D: ("S3", "Broadlink"),
|
|
199
|
+
},
|
|
200
|
+
hvac: {
|
|
201
|
+
0x4E2A: ("HVAC", "Licensed manufacturer"),
|
|
202
|
+
},
|
|
203
|
+
hysen: {
|
|
204
|
+
0x4EAD: ("HY02/HY03", "Hysen"),
|
|
205
|
+
},
|
|
206
|
+
dooya: {
|
|
207
|
+
0x4E4D: ("DT360E-45/20", "Dooya"),
|
|
208
|
+
},
|
|
209
|
+
dooya2: {
|
|
210
|
+
0x4F6E: ("DT360E-45/20", "Dooya"),
|
|
211
|
+
},
|
|
212
|
+
wser: {
|
|
213
|
+
0x4F6C: ("WSER", "Wistar"),
|
|
214
|
+
},
|
|
215
|
+
bg1: {
|
|
216
|
+
0x51E3: ("BG800/BG900", "BG Electrical"),
|
|
217
|
+
},
|
|
218
|
+
ehc31: {
|
|
219
|
+
0x6480: ("EHC31", "BG Electrical"),
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def gendevice(
|
|
225
|
+
dev_type: int,
|
|
226
|
+
host: Tuple[str, int],
|
|
227
|
+
mac: Union[bytes, str],
|
|
228
|
+
name: str = "",
|
|
229
|
+
is_locked: bool = False,
|
|
230
|
+
) -> Device:
|
|
231
|
+
"""Generate a device."""
|
|
232
|
+
for dev_cls, products in SUPPORTED_TYPES.items():
|
|
233
|
+
try:
|
|
234
|
+
model, manufacturer = products[dev_type]
|
|
235
|
+
|
|
236
|
+
except KeyError:
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
return dev_cls(
|
|
240
|
+
host,
|
|
241
|
+
mac,
|
|
242
|
+
dev_type,
|
|
243
|
+
name=name,
|
|
244
|
+
model=model,
|
|
245
|
+
manufacturer=manufacturer,
|
|
246
|
+
is_locked=is_locked,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return Device(host, mac, dev_type, name=name, is_locked=is_locked)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
async def hello(
|
|
253
|
+
ip_address: str,
|
|
254
|
+
port: int = DEFAULT_PORT,
|
|
255
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
256
|
+
) -> Device:
|
|
257
|
+
"""Direct device discovery.
|
|
258
|
+
|
|
259
|
+
Useful if the device is locked.
|
|
260
|
+
"""
|
|
261
|
+
async for device in xdiscover(
|
|
262
|
+
timeout=timeout,
|
|
263
|
+
discover_ip_address=ip_address,
|
|
264
|
+
discover_ip_port=port,
|
|
265
|
+
):
|
|
266
|
+
return device
|
|
267
|
+
raise e.NetworkTimeoutError(
|
|
268
|
+
-4000,
|
|
269
|
+
"Network timeout",
|
|
270
|
+
f"No response received within {timeout}s",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def discover(
|
|
275
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
276
|
+
local_ip_address: Optional[str] = None,
|
|
277
|
+
discover_ip_address: str = DEFAULT_BCAST_ADDR,
|
|
278
|
+
discover_ip_port: int = DEFAULT_PORT,
|
|
279
|
+
) -> List[Device]:
|
|
280
|
+
"""Discover devices connected to the local network."""
|
|
281
|
+
return [
|
|
282
|
+
device
|
|
283
|
+
async for device in xdiscover(
|
|
284
|
+
timeout, local_ip_address, discover_ip_address, discover_ip_port
|
|
285
|
+
)
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async def xdiscover(
|
|
290
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
291
|
+
local_ip_address: Optional[str] = None,
|
|
292
|
+
discover_ip_address: str = DEFAULT_BCAST_ADDR,
|
|
293
|
+
discover_ip_port: int = DEFAULT_PORT,
|
|
294
|
+
) -> AsyncIterator[Device]:
|
|
295
|
+
"""Discover devices connected to the local network.
|
|
296
|
+
|
|
297
|
+
Yields each device as soon as it answers.
|
|
298
|
+
"""
|
|
299
|
+
async for resp in scan(
|
|
300
|
+
timeout, local_ip_address, discover_ip_address, discover_ip_port
|
|
301
|
+
):
|
|
302
|
+
yield gendevice(*resp)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# Setup a new Broadlink device via AP Mode. Review the README to see how to enter AP Mode.
|
|
306
|
+
# Only tested with Broadlink RM3 Mini (Blackbean)
|
|
307
|
+
async def setup(
|
|
308
|
+
ssid: str,
|
|
309
|
+
password: str,
|
|
310
|
+
security_mode: int,
|
|
311
|
+
ip_address: str = DEFAULT_BCAST_ADDR,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Set up a new Broadlink device via AP mode."""
|
|
314
|
+
# Security mode options are (0 - none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
|
|
315
|
+
payload = bytearray(0x88)
|
|
316
|
+
payload[0x26] = 0x14 # This seems to always be set to 14
|
|
317
|
+
# Add the SSID to the payload
|
|
318
|
+
ssid_start = 68
|
|
319
|
+
ssid_length = 0
|
|
320
|
+
for letter in ssid:
|
|
321
|
+
payload[(ssid_start + ssid_length)] = ord(letter)
|
|
322
|
+
ssid_length += 1
|
|
323
|
+
# Add the WiFi password to the payload
|
|
324
|
+
pass_start = 100
|
|
325
|
+
pass_length = 0
|
|
326
|
+
for letter in password:
|
|
327
|
+
payload[(pass_start + pass_length)] = ord(letter)
|
|
328
|
+
pass_length += 1
|
|
329
|
+
|
|
330
|
+
payload[0x84] = ssid_length # Character length of SSID
|
|
331
|
+
payload[0x85] = pass_length # Character length of password
|
|
332
|
+
payload[0x86] = security_mode # Type of encryption
|
|
333
|
+
|
|
334
|
+
checksum = sum(payload, 0xBEAF) & 0xFFFF
|
|
335
|
+
payload[0x20] = checksum & 0xFF # Checksum 1 position
|
|
336
|
+
payload[0x21] = checksum >> 8 # Checksum 2 position
|
|
337
|
+
|
|
338
|
+
transport, _ = await _open_endpoint(broadcast=True)
|
|
339
|
+
try:
|
|
340
|
+
transport.sendto(payload, (ip_address, DEFAULT_PORT))
|
|
341
|
+
finally:
|
|
342
|
+
transport.close()
|
broadlink/alarm.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Support for alarm kits."""
|
|
2
|
+
from . import exceptions as e
|
|
3
|
+
from .device import Device
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class S1C(Device):
|
|
7
|
+
"""Controls a Broadlink S1C."""
|
|
8
|
+
|
|
9
|
+
TYPE = "S1C"
|
|
10
|
+
|
|
11
|
+
_SENSORS_TYPES = {
|
|
12
|
+
0x31: "Door Sensor",
|
|
13
|
+
0x91: "Key Fob",
|
|
14
|
+
0x21: "Motion Sensor",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async def get_sensors_status(self) -> dict:
|
|
18
|
+
"""Return the state of the sensors."""
|
|
19
|
+
packet = bytearray(16)
|
|
20
|
+
packet[0] = 0x06
|
|
21
|
+
response = await self.send_packet(0x6A, packet)
|
|
22
|
+
e.check_error(response[0x22:0x24])
|
|
23
|
+
payload = self.decrypt(response[0x38:])
|
|
24
|
+
count = payload[0x4]
|
|
25
|
+
sensor_data = payload[0x6:]
|
|
26
|
+
sensors = [
|
|
27
|
+
bytearray(sensor_data[i * 83 : (i + 1) * 83])
|
|
28
|
+
for i in range(len(sensor_data) // 83)
|
|
29
|
+
]
|
|
30
|
+
return {
|
|
31
|
+
"count": count,
|
|
32
|
+
"sensors": [
|
|
33
|
+
{
|
|
34
|
+
"status": sensor[0],
|
|
35
|
+
"name": sensor[4:26].decode().strip("\x00"),
|
|
36
|
+
"type": self._SENSORS_TYPES.get(sensor[3], "Unknown"),
|
|
37
|
+
"order": sensor[1],
|
|
38
|
+
"serial": sensor[26:30].hex(),
|
|
39
|
+
}
|
|
40
|
+
for sensor in sensors
|
|
41
|
+
if any(sensor[26:30])
|
|
42
|
+
],
|
|
43
|
+
}
|