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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: switchbot_api
3
- Version: 2.9.0
3
+ Version: 2.11.0
4
4
  Summary: An asynchronous library to use Switchbot API
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "switchbot_api"
3
- version = "2.9.0"
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
- return await self._request(METH_GET, f"devices/{device_id}/status")
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 pro"]
448
+ return ["Keypad", "Keypad Touch", "Keypad Vision", "Keypad Vision Pro"]
431
449
 
432
450
 
433
451
  T = TypeVar("T", bound=CommonCommands)
@@ -15,3 +15,7 @@ class SwitchBotAuthenticationError(SwitchBotError):
15
15
 
16
16
  class SwitchBotDeviceOfflineError(SwitchBotError):
17
17
  """Device currently offline."""
18
+
19
+
20
+ class SwitchBotDeviceRequestError(SwitchBotError):
21
+ """Device request error."""
@@ -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