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/switch.py ADDED
@@ -0,0 +1,472 @@
1
+ """Support for switches."""
2
+ import json
3
+ import struct
4
+ from typing import Optional
5
+
6
+ from . import exceptions as e
7
+ from .device import Device
8
+
9
+
10
+ class sp1(Device):
11
+ """Controls a Broadlink SP1."""
12
+
13
+ TYPE = "SP1"
14
+
15
+ async def set_power(self, pwr: bool) -> None:
16
+ """Set the power state of the device."""
17
+ packet = bytearray(4)
18
+ packet[0] = bool(pwr)
19
+ response = await self.send_packet(0x66, packet)
20
+ e.check_error(response[0x22:0x24])
21
+
22
+
23
+ class sp2(Device):
24
+ """Controls a Broadlink SP2."""
25
+
26
+ TYPE = "SP2"
27
+
28
+ async def set_power(self, pwr: bool) -> None:
29
+ """Set the power state of the device."""
30
+ packet = bytearray(16)
31
+ packet[0] = 2
32
+ packet[4] = bool(pwr)
33
+ response = await self.send_packet(0x6A, packet)
34
+ e.check_error(response[0x22:0x24])
35
+
36
+ async def check_power(self) -> bool:
37
+ """Return the power state of the device."""
38
+ packet = bytearray(16)
39
+ packet[0] = 1
40
+ response = await self.send_packet(0x6A, packet)
41
+ e.check_error(response[0x22:0x24])
42
+ payload = self.decrypt(response[0x38:])
43
+ return bool(payload[0x4])
44
+
45
+
46
+ class sp2s(sp2):
47
+ """Controls a Broadlink SP2S."""
48
+
49
+ TYPE = "SP2S"
50
+
51
+ async def get_energy(self) -> float:
52
+ """Return the power consumption in W."""
53
+ packet = bytearray(16)
54
+ packet[0] = 4
55
+ response = await self.send_packet(0x6A, packet)
56
+ e.check_error(response[0x22:0x24])
57
+ payload = self.decrypt(response[0x38:])
58
+ return int.from_bytes(payload[0x4:0x7], "little") / 1000
59
+
60
+
61
+ class sp3(Device):
62
+ """Controls a Broadlink SP3."""
63
+
64
+ TYPE = "SP3"
65
+
66
+ async def set_power(self, pwr: bool) -> None:
67
+ """Set the power state of the device."""
68
+ packet = bytearray(16)
69
+ packet[0] = 2
70
+ packet[4] = await self.check_nightlight() << 1 | bool(pwr)
71
+ response = await self.send_packet(0x6A, packet)
72
+ e.check_error(response[0x22:0x24])
73
+
74
+ async def set_nightlight(self, ntlight: bool) -> None:
75
+ """Set the night light state of the device."""
76
+ packet = bytearray(16)
77
+ packet[0] = 2
78
+ packet[4] = bool(ntlight) << 1 | await self.check_power()
79
+ response = await self.send_packet(0x6A, packet)
80
+ e.check_error(response[0x22:0x24])
81
+
82
+ async def check_power(self) -> bool:
83
+ """Return the power state of the device."""
84
+ packet = bytearray(16)
85
+ packet[0] = 1
86
+ response = await self.send_packet(0x6A, packet)
87
+ e.check_error(response[0x22:0x24])
88
+ payload = self.decrypt(response[0x38:])
89
+ return bool(payload[0x4] & 1)
90
+
91
+ async def check_nightlight(self) -> bool:
92
+ """Return the state of the night light."""
93
+ packet = bytearray(16)
94
+ packet[0] = 1
95
+ response = await self.send_packet(0x6A, packet)
96
+ e.check_error(response[0x22:0x24])
97
+ payload = self.decrypt(response[0x38:])
98
+ return bool(payload[0x4] & 2)
99
+
100
+
101
+ class sp3s(sp2):
102
+ """Controls a Broadlink SP3S."""
103
+
104
+ TYPE = "SP3S"
105
+
106
+ async def get_energy(self) -> float:
107
+ """Return the power consumption in W."""
108
+ packet = bytearray([8, 0, 254, 1, 5, 1, 0, 0, 0, 45])
109
+ response = await self.send_packet(0x6A, packet)
110
+ e.check_error(response[0x22:0x24])
111
+ payload = self.decrypt(response[0x38:])
112
+ energy = payload[0x7:0x4:-1].hex()
113
+ return int(energy) / 100
114
+
115
+
116
+ class sp4(Device):
117
+ """Controls a Broadlink SP4."""
118
+
119
+ TYPE = "SP4"
120
+
121
+ async def set_power(self, pwr: bool) -> None:
122
+ """Set the power state of the device."""
123
+ await self.set_state(pwr=pwr)
124
+
125
+ async def set_nightlight(self, ntlight: bool) -> None:
126
+ """Set the night light state of the device."""
127
+ await self.set_state(ntlight=ntlight)
128
+
129
+ async def set_state(
130
+ self,
131
+ pwr: Optional[bool] = None,
132
+ ntlight: Optional[bool] = None,
133
+ indicator: Optional[bool] = None,
134
+ ntlbrightness: Optional[int] = None,
135
+ maxworktime: Optional[int] = None,
136
+ childlock: Optional[bool] = None,
137
+ ) -> dict:
138
+ """Set state of device."""
139
+ state = {}
140
+ if pwr is not None:
141
+ state["pwr"] = int(bool(pwr))
142
+ if ntlight is not None:
143
+ state["ntlight"] = int(bool(ntlight))
144
+ if indicator is not None:
145
+ state["indicator"] = int(bool(indicator))
146
+ if ntlbrightness is not None:
147
+ state["ntlbrightness"] = ntlbrightness
148
+ if maxworktime is not None:
149
+ state["maxworktime"] = maxworktime
150
+ if childlock is not None:
151
+ state["childlock"] = int(bool(childlock))
152
+
153
+ packet = self._encode(2, state)
154
+ response = await self.send_packet(0x6A, packet)
155
+ return self._decode(response)
156
+
157
+ async def check_power(self) -> bool:
158
+ """Return the power state of the device."""
159
+ state = await self.get_state()
160
+ return bool(state["pwr"])
161
+
162
+ async def check_nightlight(self) -> bool:
163
+ """Return the state of the night light."""
164
+ state = await self.get_state()
165
+ return bool(state["ntlight"])
166
+
167
+ async def get_state(self) -> dict:
168
+ """Get full state of device."""
169
+ packet = self._encode(1, {})
170
+ response = await self.send_packet(0x6A, packet)
171
+ return self._decode(response)
172
+
173
+ def _encode(self, flag: int, state: dict) -> bytes:
174
+ """Encode a message."""
175
+ packet = bytearray(12)
176
+ data = json.dumps(state, separators=(",", ":")).encode()
177
+ struct.pack_into(
178
+ "<HHHBBI", packet, 0, 0xA5A5, 0x5A5A, 0x0000, flag, 0x0B, len(data)
179
+ )
180
+ packet.extend(data)
181
+ checksum = sum(packet, 0xBEAF) & 0xFFFF
182
+ packet[0x04:0x06] = checksum.to_bytes(2, "little")
183
+ return packet
184
+
185
+ def _decode(self, response: bytes) -> dict:
186
+ """Decode a message."""
187
+ e.check_error(response[0x22:0x24])
188
+ payload = self.decrypt(response[0x38:])
189
+ js_len = struct.unpack_from("<I", payload, 0x08)[0]
190
+ state = json.loads(payload[0x0C:0x0C+js_len])
191
+ return state
192
+
193
+
194
+ class sp4b(sp4):
195
+ """Controls a Broadlink SP4 (type B)."""
196
+
197
+ TYPE = "SP4B"
198
+
199
+ async def get_state(self) -> dict:
200
+ """Get full state of device."""
201
+ state = await super().get_state()
202
+
203
+ # Convert sensor data to float. Remove keys if sensors are not supported.
204
+ sensor_attrs = ["current", "volt", "power", "totalconsum", "overload"]
205
+ for attr in sensor_attrs:
206
+ value = state.pop(attr, -1)
207
+ if value != -1:
208
+ state[attr] = value / 1000
209
+ return state
210
+
211
+ def _encode(self, flag: int, state: dict) -> bytes:
212
+ """Encode a message."""
213
+ packet = bytearray(14)
214
+ data = json.dumps(state, separators=(",", ":")).encode()
215
+ length = 12 + len(data)
216
+ struct.pack_into(
217
+ "<HHHHBBI",
218
+ packet,
219
+ 0,
220
+ length,
221
+ 0xA5A5,
222
+ 0x5A5A,
223
+ 0x0000,
224
+ flag,
225
+ 0x0B,
226
+ len(data),
227
+ )
228
+ packet.extend(data)
229
+ checksum = sum(packet[0x02:], 0xBEAF) & 0xFFFF
230
+ packet[0x06:0x08] = checksum.to_bytes(2, "little")
231
+ return packet
232
+
233
+ def _decode(self, response: bytes) -> dict:
234
+ """Decode a message."""
235
+ e.check_error(response[0x22:0x24])
236
+ payload = self.decrypt(response[0x38:])
237
+ js_len = struct.unpack_from("<I", payload, 0xA)[0]
238
+ state = json.loads(payload[0x0E:0x0E+js_len])
239
+ return state
240
+
241
+
242
+ class bg1(Device):
243
+ """Controls a BG Electrical smart outlet."""
244
+
245
+ TYPE = "BG1"
246
+
247
+ async def get_state(self) -> dict:
248
+ """Return the power state of the device.
249
+
250
+ Example: `{"pwr":1,"pwr1":1,"pwr2":0,"maxworktime":60,"maxworktime1":60,"maxworktime2":0,"idcbrightness":50}`
251
+ """
252
+ packet = self._encode(1, {})
253
+ response = await self.send_packet(0x6A, packet)
254
+ e.check_error(response[0x22:0x24])
255
+ return self._decode(response)
256
+
257
+ async def set_state(
258
+ self,
259
+ pwr: Optional[bool] = None,
260
+ pwr1: Optional[bool] = None,
261
+ pwr2: Optional[bool] = None,
262
+ maxworktime: Optional[int] = None,
263
+ maxworktime1: Optional[int] = None,
264
+ maxworktime2: Optional[int] = None,
265
+ idcbrightness: Optional[int] = None,
266
+ ) -> dict:
267
+ """Set the power state of the device."""
268
+ state = {}
269
+ if pwr is not None:
270
+ state["pwr"] = int(bool(pwr))
271
+ if pwr1 is not None:
272
+ state["pwr1"] = int(bool(pwr1))
273
+ if pwr2 is not None:
274
+ state["pwr2"] = int(bool(pwr2))
275
+ if maxworktime is not None:
276
+ state["maxworktime"] = maxworktime
277
+ if maxworktime1 is not None:
278
+ state["maxworktime1"] = maxworktime1
279
+ if maxworktime2 is not None:
280
+ state["maxworktime2"] = maxworktime2
281
+ if idcbrightness is not None:
282
+ state["idcbrightness"] = idcbrightness
283
+
284
+ packet = self._encode(2, state)
285
+ response = await self.send_packet(0x6A, packet)
286
+ e.check_error(response[0x22:0x24])
287
+ return self._decode(response)
288
+
289
+ def _encode(self, flag: int, state: dict) -> bytes:
290
+ """Encode a message."""
291
+ packet = bytearray(14)
292
+ data = json.dumps(state).encode()
293
+ length = 12 + len(data)
294
+ struct.pack_into(
295
+ "<HHHHBBI",
296
+ packet,
297
+ 0,
298
+ length,
299
+ 0xA5A5,
300
+ 0x5A5A,
301
+ 0x0000,
302
+ flag,
303
+ 0x0B,
304
+ len(data),
305
+ )
306
+ packet.extend(data)
307
+ checksum = sum(packet[0x2:], 0xBEAF) & 0xFFFF
308
+ packet[0x06:0x08] = checksum.to_bytes(2, "little")
309
+ return packet
310
+
311
+ def _decode(self, response: bytes) -> dict:
312
+ """Decode a message."""
313
+ payload = self.decrypt(response[0x38:])
314
+ js_len = struct.unpack_from("<I", payload, 0x0A)[0]
315
+ state = json.loads(payload[0x0E:0x0E+js_len])
316
+ return state
317
+
318
+
319
+ class ehc31(bg1):
320
+ """Controls a BG Electrical smart extension lead."""
321
+
322
+ TYPE = "EHC31"
323
+
324
+ async def set_state(
325
+ self,
326
+ pwr: Optional[bool] = None,
327
+ pwr1: Optional[bool] = None,
328
+ pwr2: Optional[bool] = None,
329
+ pwr3: Optional[bool] = None,
330
+ maxworktime1: Optional[int] = None,
331
+ maxworktime2: Optional[int] = None,
332
+ maxworktime3: Optional[int] = None,
333
+ idcbrightness: Optional[int] = None,
334
+ childlock: Optional[bool] = None,
335
+ childlock1: Optional[bool] = None,
336
+ childlock2: Optional[bool] = None,
337
+ childlock3: Optional[bool] = None,
338
+ childlock4: Optional[bool] = None,
339
+ ) -> dict:
340
+ """Set the power state of the device."""
341
+ state = {}
342
+ if pwr is not None:
343
+ state["pwr"] = int(bool(pwr))
344
+ if pwr1 is not None:
345
+ state["pwr1"] = int(bool(pwr1))
346
+ if pwr2 is not None:
347
+ state["pwr2"] = int(bool(pwr2))
348
+ if pwr3 is not None:
349
+ state["pwr3"] = int(bool(pwr3))
350
+ if maxworktime1 is not None:
351
+ state["maxworktime1"] = maxworktime1
352
+ if maxworktime2 is not None:
353
+ state["maxworktime2"] = maxworktime2
354
+ if maxworktime3 is not None:
355
+ state["maxworktime3"] = maxworktime3
356
+ if idcbrightness is not None:
357
+ state["idcbrightness"] = idcbrightness
358
+ if childlock is not None:
359
+ state["childlock"] = int(bool(childlock))
360
+ if childlock1 is not None:
361
+ state["childlock1"] = int(bool(childlock1))
362
+ if childlock2 is not None:
363
+ state["childlock2"] = int(bool(childlock2))
364
+ if childlock3 is not None:
365
+ state["childlock3"] = int(bool(childlock3))
366
+ if childlock4 is not None:
367
+ state["childlock4"] = int(bool(childlock4))
368
+
369
+ packet = self._encode(2, state)
370
+ response = await self.send_packet(0x6A, packet)
371
+ e.check_error(response[0x22:0x24])
372
+ return self._decode(response)
373
+
374
+
375
+ class mp1(Device):
376
+ """Controls a Broadlink MP1."""
377
+
378
+ TYPE = "MP1"
379
+
380
+ async def set_power_mask(self, sid_mask: int, pwr: bool) -> None:
381
+ """Set the power state of the device."""
382
+ packet = bytearray(16)
383
+ packet[0x00] = 0x0D
384
+ packet[0x02] = 0xA5
385
+ packet[0x03] = 0xA5
386
+ packet[0x04] = 0x5A
387
+ packet[0x05] = 0x5A
388
+ packet[0x06] = 0xB2 + ((sid_mask << 1) if pwr else sid_mask)
389
+ packet[0x07] = 0xC0
390
+ packet[0x08] = 0x02
391
+ packet[0x0A] = 0x03
392
+ packet[0x0D] = sid_mask
393
+ packet[0x0E] = sid_mask if pwr else 0
394
+
395
+ response = await self.send_packet(0x6A, packet)
396
+ e.check_error(response[0x22:0x24])
397
+
398
+ async def set_power(self, sid: int, pwr: bool) -> None:
399
+ """Set the power state of the device."""
400
+ sid_mask = 0x01 << (sid - 1)
401
+ await self.set_power_mask(sid_mask, pwr)
402
+
403
+ async def check_power_raw(self) -> int:
404
+ """Return the power state of the device in raw format."""
405
+ packet = bytearray(16)
406
+ packet[0x00] = 0x0A
407
+ packet[0x02] = 0xA5
408
+ packet[0x03] = 0xA5
409
+ packet[0x04] = 0x5A
410
+ packet[0x05] = 0x5A
411
+ packet[0x06] = 0xAE
412
+ packet[0x07] = 0xC0
413
+ packet[0x08] = 0x01
414
+
415
+ response = await self.send_packet(0x6A, packet)
416
+ e.check_error(response[0x22:0x24])
417
+ payload = self.decrypt(response[0x38:])
418
+ return payload[0x0E]
419
+
420
+ async def check_power(self) -> dict:
421
+ """Return the power state of the device."""
422
+ data = await self.check_power_raw()
423
+ return {
424
+ "s1": bool(data & 1),
425
+ "s2": bool(data & 2),
426
+ "s3": bool(data & 4),
427
+ "s4": bool(data & 8),
428
+ }
429
+
430
+
431
+ class mp1s(mp1):
432
+ """Controls a Broadlink MP1S."""
433
+
434
+ TYPE = "MP1S"
435
+
436
+ async def get_state(self) -> dict:
437
+ """Return the power state of the device.
438
+
439
+ voltage in V.
440
+ current in A.
441
+ power in W.
442
+ power consumption in kW·h.
443
+ """
444
+ packet = bytearray(16)
445
+ packet[0x00] = 0x0E
446
+ packet[0x02] = 0xA5
447
+ packet[0x03] = 0xA5
448
+ packet[0x04] = 0x5A
449
+ packet[0x05] = 0x5A
450
+ packet[0x06] = 0xB2
451
+ packet[0x07] = 0xC0
452
+ packet[0x08] = 0x01
453
+ packet[0x0A] = 0x04
454
+
455
+ response = await self.send_packet(0x6A, packet)
456
+ e.check_error(response[0x22:0x24])
457
+ payload = self.decrypt(response[0x38:])
458
+ payload_str = payload.hex()[4:-6]
459
+
460
+ def get_value(start, end, factors):
461
+ value = sum(
462
+ int(payload_str[i-2:i]) * factor
463
+ for i, factor in zip(range(start, end, -2), factors)
464
+ )
465
+ return value
466
+
467
+ return {
468
+ "volt": get_value(34, 30, [10, 0.1]),
469
+ "current": get_value(40, 34, [1, 0.01, 0.0001]),
470
+ "power": get_value(46, 40, [100, 1, 0.01]),
471
+ "totalconsum": get_value(54, 46, [10000, 100, 1, 0.01]),
472
+ }