PySwitchbot 0.45.0__tar.gz → 0.46.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.
Files changed (52) hide show
  1. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PKG-INFO +2 -3
  2. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PySwitchbot.egg-info/PKG-INFO +2 -3
  3. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PySwitchbot.egg-info/requires.txt +1 -2
  4. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/setup.py +2 -3
  5. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/__init__.py +2 -0
  6. pyswitchbot-0.46.0/switchbot/api_config.py +6 -0
  7. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/const.py +8 -0
  8. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/base_cover.py +0 -1
  9. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/lock.py +77 -60
  10. PySwitchbot-0.45.0/switchbot/api_config.py +0 -13
  11. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/LICENSE +0 -0
  12. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/MANIFEST.in +0 -0
  13. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PySwitchbot.egg-info/SOURCES.txt +0 -0
  14. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  15. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/PySwitchbot.egg-info/top_level.txt +0 -0
  16. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/README.md +0 -0
  17. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/setup.cfg +0 -0
  18. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parser.py +0 -0
  19. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/__init__.py +0 -0
  20. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
  21. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/bot.py +0 -0
  22. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/bulb.py +0 -0
  23. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  24. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/contact.py +0 -0
  25. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/curtain.py +0 -0
  26. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/humidifier.py +0 -0
  27. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/light_strip.py +0 -0
  28. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/lock.py +0 -0
  29. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/meter.py +0 -0
  30. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/motion.py +0 -0
  31. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/adv_parsers/plug.py +0 -0
  32. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/__init__.py +0 -0
  33. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/base_light.py +0 -0
  34. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/blind_tilt.py +0 -0
  35. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/bot.py +0 -0
  36. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/bulb.py +0 -0
  37. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/ceiling_light.py +0 -0
  38. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/contact.py +0 -0
  39. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/curtain.py +0 -0
  40. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/device.py +0 -0
  41. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/humidifier.py +0 -0
  42. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/light_strip.py +0 -0
  43. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/meter.py +0 -0
  44. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/motion.py +0 -0
  45. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/devices/plug.py +0 -0
  46. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/discovery.py +0 -0
  47. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/enum.py +0 -0
  48. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/switchbot/models.py +0 -0
  49. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/tests/test_adv_parser.py +0 -0
  50. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/tests/test_base_cover.py +0 -0
  51. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/tests/test_blind_tilt.py +0 -0
  52. {PySwitchbot-0.45.0 → pyswitchbot-0.46.0}/tests/test_curtain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.45.0
3
+ Version: 0.46.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -13,9 +13,8 @@ Classifier: Programming Language :: Python
13
13
  Classifier: Topic :: Home Automation
14
14
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
15
  License-File: LICENSE
16
+ Requires-Dist: aiohttp>=3.9.5
16
17
  Requires-Dist: bleak>=0.19.0
17
18
  Requires-Dist: bleak-retry-connector>=3.4.0
18
19
  Requires-Dist: cryptography>=39.0.0
19
20
  Requires-Dist: pyOpenSSL>=23.0.0
20
- Requires-Dist: boto3>=1.20.24
21
- Requires-Dist: requests>=2.28.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.45.0
3
+ Version: 0.46.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -13,9 +13,8 @@ Classifier: Programming Language :: Python
13
13
  Classifier: Topic :: Home Automation
14
14
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
15
  License-File: LICENSE
16
+ Requires-Dist: aiohttp>=3.9.5
16
17
  Requires-Dist: bleak>=0.19.0
17
18
  Requires-Dist: bleak-retry-connector>=3.4.0
18
19
  Requires-Dist: cryptography>=39.0.0
19
20
  Requires-Dist: pyOpenSSL>=23.0.0
20
- Requires-Dist: boto3>=1.20.24
21
- Requires-Dist: requests>=2.28.1
@@ -1,6 +1,5 @@
1
+ aiohttp>=3.9.5
1
2
  bleak>=0.19.0
2
3
  bleak-retry-connector>=3.4.0
3
4
  cryptography>=39.0.0
4
5
  pyOpenSSL>=23.0.0
5
- boto3>=1.20.24
6
- requests>=2.28.1
@@ -4,14 +4,13 @@ setup(
4
4
  name="PySwitchbot",
5
5
  packages=["switchbot", "switchbot.devices", "switchbot.adv_parsers"],
6
6
  install_requires=[
7
+ "aiohttp>=3.9.5",
7
8
  "bleak>=0.19.0",
8
9
  "bleak-retry-connector>=3.4.0",
9
10
  "cryptography>=39.0.0",
10
11
  "pyOpenSSL>=23.0.0",
11
- "boto3>=1.20.24",
12
- "requests>=2.28.1",
13
12
  ],
14
- version="0.45.0",
13
+ version="0.46.0",
15
14
  description="A library to communicate with Switchbot",
16
15
  author="Daniel Hjelseth Hoyer",
17
16
  url="https://github.com/Danielhiversen/pySwitchbot/",
@@ -11,6 +11,7 @@ from .adv_parser import SwitchbotSupportedType, parse_advertisement_data
11
11
  from .const import (
12
12
  LockStatus,
13
13
  SwitchbotAccountConnectionError,
14
+ SwitchbotApiError,
14
15
  SwitchbotAuthenticationError,
15
16
  SwitchbotModel,
16
17
  )
@@ -36,6 +37,7 @@ __all__ = [
36
37
  "GetSwitchbotDevices",
37
38
  "SwitchBotAdvertisement",
38
39
  "SwitchbotAccountConnectionError",
40
+ "SwitchbotApiError",
39
41
  "SwitchbotAuthenticationError",
40
42
  "ColorMode",
41
43
  "LockStatus",
@@ -0,0 +1,6 @@
1
+ # Those values have been obtained from the following files in SwitchBot Android app
2
+ # That's how you can verify them yourself
3
+ # /assets/switchbot_config.json
4
+
5
+ SWITCHBOT_APP_API_BASE_URL = "api.switchbot.net"
6
+ SWITCHBOT_APP_CLIENT_ID = "5nnwmhmsa9xxskm14hd85lm9bm"
@@ -10,6 +10,14 @@ DEFAULT_RETRY_TIMEOUT = 1
10
10
  DEFAULT_SCAN_TIMEOUT = 5
11
11
 
12
12
 
13
+ class SwitchbotApiError(RuntimeError):
14
+ """Raised when API call fails.
15
+
16
+ This exception inherits from RuntimeError to avoid breaking existing code
17
+ but will be changed to Exception in a future release.
18
+ """
19
+
20
+
13
21
  class SwitchbotAuthenticationError(RuntimeError):
14
22
  """Raised when authentication fails.
15
23
 
@@ -5,7 +5,6 @@ import logging
5
5
  from abc import abstractmethod
6
6
  from typing import Any
7
7
 
8
- from ..models import SwitchBotAdvertisement
9
8
  from .device import REQ_HEADER, SwitchbotDevice, update_after_operation
10
9
 
11
10
  # Cover keys
@@ -1,23 +1,20 @@
1
1
  """Library to handle connection with Switchbot Lock."""
2
2
  from __future__ import annotations
3
3
 
4
- import base64
5
- import hashlib
6
- import hmac
7
- import json
4
+ import asyncio
8
5
  import logging
9
6
  import time
10
7
  from typing import Any
11
8
 
12
- import boto3
13
- import requests
9
+ import aiohttp
14
10
  from bleak.backends.device import BLEDevice
15
11
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
16
12
 
17
- from ..api_config import SWITCHBOT_APP_API_BASE_URL, SWITCHBOT_APP_COGNITO_POOL
13
+ from ..api_config import SWITCHBOT_APP_API_BASE_URL, SWITCHBOT_APP_CLIENT_ID
18
14
  from ..const import (
19
15
  LockStatus,
20
16
  SwitchbotAccountConnectionError,
17
+ SwitchbotApiError,
21
18
  SwitchbotAuthenticationError,
22
19
  )
23
20
  from .device import SwitchbotDevice, SwitchbotOperationError
@@ -86,77 +83,97 @@ class SwitchbotLock(SwitchbotDevice):
86
83
 
87
84
  return lock_info is not None
88
85
 
86
+ @staticmethod
87
+ async def api_request(
88
+ session: aiohttp.ClientSession,
89
+ subdomain: str,
90
+ path: str,
91
+ data: dict = None,
92
+ headers: dict = None,
93
+ ) -> dict:
94
+ url = f"https://{subdomain}.{SWITCHBOT_APP_API_BASE_URL}/{path}"
95
+ async with session.post(url, json=data, headers=headers) as result:
96
+ if result.status > 299:
97
+ raise SwitchbotApiError(
98
+ f"Unexpected status code returned by SwitchBot API: {result.status}"
99
+ )
100
+
101
+ response = await result.json()
102
+ if response["statusCode"] != 100:
103
+ raise SwitchbotApiError(
104
+ f"{response['message']}, status code: {response['statusCode']}"
105
+ )
106
+
107
+ return response["body"]
108
+
109
+ # Old non-async method preserved for backwards compatibility
89
110
  @staticmethod
90
111
  def retrieve_encryption_key(device_mac: str, username: str, password: str):
112
+ async def async_fn():
113
+ async with aiohttp.ClientSession() as session:
114
+ return await SwitchbotLock.async_retrieve_encryption_key(
115
+ session, device_mac, username, password
116
+ )
117
+
118
+ return asyncio.run(async_fn())
119
+
120
+ @staticmethod
121
+ async def async_retrieve_encryption_key(
122
+ session: aiohttp.ClientSession, device_mac: str, username: str, password: str
123
+ ) -> dict:
91
124
  """Retrieve lock key from internal SwitchBot API."""
92
125
  device_mac = device_mac.replace(":", "").replace("-", "").upper()
93
- msg = bytes(username + SWITCHBOT_APP_COGNITO_POOL["AppClientId"], "utf-8")
94
- secret_hash = base64.b64encode(
95
- hmac.new(
96
- SWITCHBOT_APP_COGNITO_POOL["AppClientSecret"].encode(),
97
- msg,
98
- digestmod=hashlib.sha256,
99
- ).digest()
100
- ).decode()
101
-
102
- cognito_idp_client = boto3.client(
103
- "cognito-idp", region_name=SWITCHBOT_APP_COGNITO_POOL["Region"]
104
- )
126
+
105
127
  try:
106
- auth_response = cognito_idp_client.initiate_auth(
107
- ClientId=SWITCHBOT_APP_COGNITO_POOL["AppClientId"],
108
- AuthFlow="USER_PASSWORD_AUTH",
109
- AuthParameters={
110
- "USERNAME": username,
111
- "PASSWORD": password,
112
- "SECRET_HASH": secret_hash,
128
+ auth_result = await SwitchbotLock.api_request(
129
+ session,
130
+ "account",
131
+ "account/api/v1/user/login",
132
+ {
133
+ "clientId": SWITCHBOT_APP_CLIENT_ID,
134
+ "username": username,
135
+ "password": password,
136
+ "grantType": "password",
137
+ "verifyCode": "",
113
138
  },
114
139
  )
115
- except cognito_idp_client.exceptions.NotAuthorizedException as err:
116
- raise SwitchbotAuthenticationError(
117
- f"Failed to authenticate: {err}"
118
- ) from err
140
+ auth_headers = {"authorization": auth_result["access_token"]}
119
141
  except Exception as err:
120
- raise SwitchbotAuthenticationError(
121
- f"Unexpected error during authentication: {err}"
122
- ) from err
142
+ raise SwitchbotAuthenticationError(f"Authentication failed: {err}") from err
123
143
 
124
- if (
125
- auth_response is None
126
- or "AuthenticationResult" not in auth_response
127
- or "AccessToken" not in auth_response["AuthenticationResult"]
128
- ):
129
- raise SwitchbotAuthenticationError("Unexpected authentication response")
144
+ try:
145
+ userinfo = await SwitchbotLock.api_request(
146
+ session, "account", "account/api/v1/user/userinfo", {}, auth_headers
147
+ )
148
+ if "botRegion" in userinfo and userinfo["botRegion"] != "":
149
+ region = userinfo["botRegion"]
150
+ else:
151
+ region = "us"
152
+ except Exception as err:
153
+ raise SwitchbotAccountConnectionError(
154
+ f"Failed to retrieve SwitchBot Account user details: {err}"
155
+ ) from err
130
156
 
131
- access_token = auth_response["AuthenticationResult"]["AccessToken"]
132
157
  try:
133
- key_response = requests.post(
134
- url=SWITCHBOT_APP_API_BASE_URL + "/developStage/keys/v1/communicate",
135
- headers={"authorization": access_token},
136
- json={
158
+ device_info = await SwitchbotLock.api_request(
159
+ session,
160
+ f"wonderlabs.{region}",
161
+ "wonder/keys/v1/communicate",
162
+ {
137
163
  "device_mac": device_mac,
138
164
  "keyType": "user",
139
165
  },
140
- timeout=10,
166
+ auth_headers,
141
167
  )
142
- except requests.exceptions.RequestException as err:
168
+
169
+ return {
170
+ "key_id": device_info["communicationKey"]["keyId"],
171
+ "encryption_key": device_info["communicationKey"]["key"],
172
+ }
173
+ except Exception as err:
143
174
  raise SwitchbotAccountConnectionError(
144
175
  f"Failed to retrieve encryption key from SwitchBot Account: {err}"
145
176
  ) from err
146
- if key_response.status_code > 299:
147
- raise SwitchbotAuthenticationError(
148
- f"Unexpected status code returned by SwitchBot Account API: {key_response.status_code}"
149
- )
150
- key_response_content = json.loads(key_response.content)
151
- if key_response_content["statusCode"] != 100:
152
- raise SwitchbotAuthenticationError(
153
- f"Unexpected status code returned by SwitchBot API: {key_response_content['statusCode']}"
154
- )
155
-
156
- return {
157
- "key_id": key_response_content["body"]["communicationKey"]["keyId"],
158
- "encryption_key": key_response_content["body"]["communicationKey"]["key"],
159
- }
160
177
 
161
178
  async def lock(self) -> bool:
162
179
  """Send lock command."""
@@ -1,13 +0,0 @@
1
- # Those values have been obtained from the following files in SwitchBot Android app
2
- # That's how you can verify them yourself
3
- # /assets/switchbot_config.json
4
- # /res/raw/amplifyconfiguration.json
5
- # /res/raw/awsconfiguration.json
6
-
7
- SWITCHBOT_APP_API_BASE_URL = "https://l9ren7efdj.execute-api.us-east-1.amazonaws.com"
8
- SWITCHBOT_APP_COGNITO_POOL = {
9
- "PoolId": "us-east-1_x1fixo5LC",
10
- "AppClientId": "66r90hdllaj4nnlne4qna0muls",
11
- "AppClientSecret": "1v3v7vfjsiggiupkeuqvsovg084e3msbefpj9rgh611u30uug6t8",
12
- "Region": "us-east-1",
13
- }
File without changes
File without changes
File without changes
File without changes