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 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
+ }