switchbot-api 2.9.0__tar.gz → 2.11.0__tar.gz
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.
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/PKG-INFO +1 -1
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/pyproject.toml +2 -1
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/switchbot_api/__init__.py +45 -1
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/switchbot_api/commands.py +20 -2
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/switchbot_api/exceptions.py +4 -0
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/switchbot_api/models.py +17 -0
- switchbot_api-2.11.0/switchbot_api/utils.py +28 -0
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/LICENSE +0 -0
- {switchbot_api-2.9.0 → switchbot_api-2.11.0}/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "switchbot_api"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.11.0"
|
|
4
4
|
description = "An asynchronous library to use Switchbot API"
|
|
5
5
|
authors = ["Ravaka Razafimanantsoa <contact@ravaka.dev>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -28,6 +28,7 @@ codespell = "^2.3.0"
|
|
|
28
28
|
pre-commit = "^4.0.1"
|
|
29
29
|
pre-commit-hooks = "^5.0.0"
|
|
30
30
|
yamllint = "^1.35.1"
|
|
31
|
+
pycryptodome = "^3.23.0"
|
|
31
32
|
|
|
32
33
|
[tool.mypy]
|
|
33
34
|
# Specify the target platform details in config, so your developers are
|
|
@@ -14,10 +14,13 @@ import uuid
|
|
|
14
14
|
|
|
15
15
|
from aiohttp import ClientError, ClientResponseError, ClientSession
|
|
16
16
|
from aiohttp.hdrs import METH_GET, METH_POST
|
|
17
|
+
from Crypto.Cipher import AES
|
|
18
|
+
from Crypto.Util.Padding import unpad
|
|
17
19
|
|
|
18
20
|
from switchbot_api.commands import (
|
|
19
21
|
AirConditionerCommands,
|
|
20
22
|
AirPurifierCommands,
|
|
23
|
+
ArtFrameCommands,
|
|
21
24
|
BatteryCirculatorFanCommands,
|
|
22
25
|
BlindTiltCommands,
|
|
23
26
|
BotCommands,
|
|
@@ -30,9 +33,11 @@ from switchbot_api.commands import (
|
|
|
30
33
|
FanCommands,
|
|
31
34
|
HumidifierCommands,
|
|
32
35
|
HumidifierV2Commands,
|
|
36
|
+
KeyPadCommands,
|
|
33
37
|
LightCommands,
|
|
34
38
|
LockCommands,
|
|
35
39
|
LockV2Commands,
|
|
40
|
+
LockV3Commands,
|
|
36
41
|
OthersCommands,
|
|
37
42
|
RGBWLightCommands,
|
|
38
43
|
RGBWWLightCommands,
|
|
@@ -53,7 +58,10 @@ from switchbot_api.exceptions import (
|
|
|
53
58
|
SwitchBotDeviceOfflineError,
|
|
54
59
|
)
|
|
55
60
|
from switchbot_api.models import (
|
|
61
|
+
AirPurifierFanGear,
|
|
62
|
+
AirPurifierMode,
|
|
56
63
|
BatteryCirculatorFanMode,
|
|
64
|
+
BatteryLevel,
|
|
57
65
|
PowerState,
|
|
58
66
|
SmartRadiatorThermostatMode,
|
|
59
67
|
VacuumCleanMode,
|
|
@@ -64,8 +72,12 @@ from switchbot_api.models import (
|
|
|
64
72
|
__all__ = [
|
|
65
73
|
"AirConditionerCommands",
|
|
66
74
|
"AirPurifierCommands",
|
|
75
|
+
"AirPurifierFanGear",
|
|
76
|
+
"AirPurifierMode",
|
|
77
|
+
"ArtFrameCommands",
|
|
67
78
|
"BatteryCirculatorFanCommands",
|
|
68
79
|
"BatteryCirculatorFanMode",
|
|
80
|
+
"BatteryLevel",
|
|
69
81
|
"BlindTiltCommands",
|
|
70
82
|
"BotCommands",
|
|
71
83
|
"CeilingLightCommands",
|
|
@@ -78,9 +90,11 @@ __all__ = [
|
|
|
78
90
|
"FanCommands",
|
|
79
91
|
"HumidifierCommands",
|
|
80
92
|
"HumidifierV2Commands",
|
|
93
|
+
"KeyPadCommands",
|
|
81
94
|
"LightCommands",
|
|
82
95
|
"LockCommands",
|
|
83
96
|
"LockV2Commands",
|
|
97
|
+
"LockV3Commands",
|
|
84
98
|
"OthersCommands",
|
|
85
99
|
"PowerState",
|
|
86
100
|
"RGBWLightCommands",
|
|
@@ -236,9 +250,22 @@ class SwitchBotAPI:
|
|
|
236
250
|
]
|
|
237
251
|
return [*devices, *remotes]
|
|
238
252
|
|
|
253
|
+
async def get_device(self, device_id: str) -> dict[str, Any]:
|
|
254
|
+
"""Get target device."""
|
|
255
|
+
body = await self._request(METH_GET, "devices")
|
|
256
|
+
for device in body["deviceList"]:
|
|
257
|
+
if device_id == device["deviceId"]:
|
|
258
|
+
return device # type: ignore[no-any-return]
|
|
259
|
+
return {}
|
|
260
|
+
|
|
239
261
|
async def get_status(self, device_id: str) -> dict[str, Any]:
|
|
240
262
|
"""No status for IR devices."""
|
|
241
|
-
|
|
263
|
+
response = await self._request(METH_GET, f"devices/{device_id}/status")
|
|
264
|
+
|
|
265
|
+
device_type = response["deviceType"]
|
|
266
|
+
if device_type and device_type in KeyPadCommands.get_supported_devices():
|
|
267
|
+
return await self.get_device(device_id)
|
|
268
|
+
return response
|
|
242
269
|
|
|
243
270
|
async def get_webook_configuration(self) -> dict[str, Any]:
|
|
244
271
|
"""List webhooks."""
|
|
@@ -292,6 +319,23 @@ class SwitchBotAPI:
|
|
|
292
319
|
if self.session and self._close_session:
|
|
293
320
|
await self.session.close()
|
|
294
321
|
|
|
322
|
+
def aes_128_cbc_decrypt(self, encrypt_text: str, aes_iv: str) -> str:
|
|
323
|
+
"""Aes 128 cbc decrypt.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
encrypt_text (str):
|
|
327
|
+
aes_iv (str):
|
|
328
|
+
|
|
329
|
+
Notes : only for [Keypad, Keypad Touch, Keypad Vision, Keypad Vision Pro]
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
encrypt_bytes = base64.b64decode(encrypt_text)
|
|
333
|
+
cipher = AES.new(
|
|
334
|
+
bytes.fromhex(self.secret), AES.MODE_CBC, bytes.fromhex(aes_iv)
|
|
335
|
+
)
|
|
336
|
+
decrypt_bytes = unpad(cipher.decrypt(encrypt_bytes), AES.block_size)
|
|
337
|
+
return decrypt_bytes.decode("utf-8")
|
|
338
|
+
|
|
295
339
|
async def __aenter__(self) -> Self:
|
|
296
340
|
"""Async enter.
|
|
297
341
|
|
|
@@ -93,7 +93,21 @@ class LockV2Commands(Commands):
|
|
|
93
93
|
@classmethod
|
|
94
94
|
def get_supported_devices(cls) -> list[str]:
|
|
95
95
|
"""Get supported devices."""
|
|
96
|
-
return ["Smart Lock", "Smart Lock Pro", "Smart Lock Ultra"]
|
|
96
|
+
return ["Smart Lock", "Smart Lock Pro", "Smart Lock Ultra", "Smart Lock Vision"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class LockV3Commands(Commands):
|
|
100
|
+
"""Lock commands."""
|
|
101
|
+
|
|
102
|
+
LOCK = "lock"
|
|
103
|
+
UNLOCK = "unlock"
|
|
104
|
+
DEADBOLT = "deadbolt"
|
|
105
|
+
NIGHT_LATCH_UNLOCK = "nightLatchUnlock"
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def get_supported_devices(cls) -> list[str]:
|
|
109
|
+
"""Get supported devices."""
|
|
110
|
+
return ["Smart Lock Vision Pro"]
|
|
97
111
|
|
|
98
112
|
|
|
99
113
|
class HumidifierCommands(Commands):
|
|
@@ -134,6 +148,9 @@ class AirPurifierCommands(Commands):
|
|
|
134
148
|
|
|
135
149
|
SET_MODE = "setMode"
|
|
136
150
|
SET_CHILD_LOCK = "setChildLock"
|
|
151
|
+
SET_LIGHT_COLOR = "setLightColor"
|
|
152
|
+
SET_BRIGHTNESS = "setBrightness"
|
|
153
|
+
SET_LIGHT_SENSOR = "setLightSensor"
|
|
137
154
|
|
|
138
155
|
@classmethod
|
|
139
156
|
def get_supported_devices(cls) -> list[str]:
|
|
@@ -245,6 +262,7 @@ class ArtFrameCommands(Commands):
|
|
|
245
262
|
|
|
246
263
|
PREVIOUS = "previous"
|
|
247
264
|
NEXT = "next"
|
|
265
|
+
UPLOAD = "uploadImage"
|
|
248
266
|
|
|
249
267
|
@classmethod
|
|
250
268
|
def get_supported_devices(cls) -> list[str]:
|
|
@@ -427,7 +445,7 @@ class KeyPadCommands(Commands):
|
|
|
427
445
|
@classmethod
|
|
428
446
|
def get_supported_devices(cls) -> list[str]:
|
|
429
447
|
"""Get supported devices."""
|
|
430
|
-
return ["Keypad", "Keypad Touch", "Keypad Vision", "Keypad Vision
|
|
448
|
+
return ["Keypad", "Keypad Touch", "Keypad Vision", "Keypad Vision Pro"]
|
|
431
449
|
|
|
432
450
|
|
|
433
451
|
T = TypeVar("T", bound=CommonCommands)
|
|
@@ -21,6 +21,23 @@ class BatteryCirculatorFanMode(StrEnum):
|
|
|
21
21
|
BABY = "baby"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class AirPurifierMode(Enum):
|
|
25
|
+
"""mode types [Air Purifier] API."""
|
|
26
|
+
|
|
27
|
+
LEVEL = 1
|
|
28
|
+
AUTO = 2
|
|
29
|
+
SLEEP = 3
|
|
30
|
+
PET = 4
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AirPurifierFanGear(Enum):
|
|
34
|
+
"""Air Purifier Fan Gear."""
|
|
35
|
+
|
|
36
|
+
High = 3
|
|
37
|
+
Medium = 2
|
|
38
|
+
Low = 1
|
|
39
|
+
|
|
40
|
+
|
|
24
41
|
class VacuumFanSpeed(StrEnum):
|
|
25
42
|
"""Fan options for VacuumCommands supported devices."""
|
|
26
43
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""util for SwitchBot API."""
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
from aiohttp import ClientResponse
|
|
5
|
+
|
|
6
|
+
from .exceptions import SwitchBotDeviceRequestError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def check_response_status(response: ClientResponse) -> None:
|
|
10
|
+
"""Check https response status."""
|
|
11
|
+
if response.status != 200:
|
|
12
|
+
msg = f"status code != 200 (actual: {response.status})"
|
|
13
|
+
raise SwitchBotDeviceRequestError(msg)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def get_file_stream_from_cloud(url: str, timeout: float = 5) -> bytes:
|
|
17
|
+
"""Get file stream from cloud."""
|
|
18
|
+
# now only for download <AI Art Frame> Picture
|
|
19
|
+
try:
|
|
20
|
+
async with (
|
|
21
|
+
aiohttp.ClientSession() as session,
|
|
22
|
+
session.get(url, timeout=aiohttp.ClientTimeout(total=timeout)) as response,
|
|
23
|
+
):
|
|
24
|
+
check_response_status(response)
|
|
25
|
+
return await response.read()
|
|
26
|
+
except Exception as e:
|
|
27
|
+
msg = f"{e}"
|
|
28
|
+
raise SwitchBotDeviceRequestError(msg) from e
|
|
File without changes
|
|
File without changes
|