PySwitchbot 0.46.0__tar.gz → 0.47.2__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.46.0 → pyswitchbot-0.47.2}/PKG-INFO +1 -1
  2. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/PySwitchbot.egg-info/SOURCES.txt +1 -0
  4. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/setup.py +1 -1
  5. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parser.py +8 -1
  6. pyswitchbot-0.47.2/switchbot/adv_parsers/hub2.py +40 -0
  7. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/const.py +1 -0
  8. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/lock.py +6 -1
  9. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/discovery.py +3 -2
  10. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/tests/test_adv_parser.py +34 -37
  11. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/LICENSE +0 -0
  12. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/MANIFEST.in +0 -0
  13. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  14. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/PySwitchbot.egg-info/requires.txt +0 -0
  15. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/PySwitchbot.egg-info/top_level.txt +0 -0
  16. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/README.md +0 -0
  17. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/setup.cfg +0 -0
  18. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/__init__.py +0 -0
  19. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/__init__.py +0 -0
  20. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/blind_tilt.py +0 -0
  21. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/bot.py +0 -0
  22. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/bulb.py +0 -0
  23. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/ceiling_light.py +0 -0
  24. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/contact.py +0 -0
  25. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/curtain.py +0 -0
  26. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/humidifier.py +0 -0
  27. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/light_strip.py +0 -0
  28. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/lock.py +0 -0
  29. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/meter.py +0 -0
  30. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/motion.py +0 -0
  31. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/adv_parsers/plug.py +0 -0
  32. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/api_config.py +0 -0
  33. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/__init__.py +0 -0
  34. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/base_cover.py +0 -0
  35. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/base_light.py +0 -0
  36. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/blind_tilt.py +0 -0
  37. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/bot.py +0 -0
  38. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/bulb.py +0 -0
  39. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/ceiling_light.py +0 -0
  40. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/contact.py +0 -0
  41. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/curtain.py +0 -0
  42. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/device.py +0 -0
  43. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/humidifier.py +0 -0
  44. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/light_strip.py +0 -0
  45. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/meter.py +0 -0
  46. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/motion.py +0 -0
  47. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/devices/plug.py +0 -0
  48. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/enum.py +0 -0
  49. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/switchbot/models.py +0 -0
  50. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/tests/test_base_cover.py +0 -0
  51. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/tests/test_blind_tilt.py +0 -0
  52. {pyswitchbot-0.46.0 → pyswitchbot-0.47.2}/tests/test_curtain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.46.0
3
+ Version: 0.47.2
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.46.0
3
+ Version: 0.47.2
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -21,6 +21,7 @@ switchbot/adv_parsers/bulb.py
21
21
  switchbot/adv_parsers/ceiling_light.py
22
22
  switchbot/adv_parsers/contact.py
23
23
  switchbot/adv_parsers/curtain.py
24
+ switchbot/adv_parsers/hub2.py
24
25
  switchbot/adv_parsers/humidifier.py
25
26
  switchbot/adv_parsers/light_strip.py
26
27
  switchbot/adv_parsers/lock.py
@@ -10,7 +10,7 @@ setup(
10
10
  "cryptography>=39.0.0",
11
11
  "pyOpenSSL>=23.0.0",
12
12
  ],
13
- version="0.46.0",
13
+ version="0.47.2",
14
14
  description="A library to communicate with Switchbot",
15
15
  author="Daniel Hjelseth Hoyer",
16
16
  url="https://github.com/Danielhiversen/pySwitchbot/",
@@ -1,4 +1,5 @@
1
1
  """Library to handle connection with Switchbot."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import logging
@@ -15,6 +16,7 @@ from .adv_parsers.bulb import process_color_bulb
15
16
  from .adv_parsers.ceiling_light import process_woceiling
16
17
  from .adv_parsers.contact import process_wocontact
17
18
  from .adv_parsers.curtain import process_wocurtain
19
+ from .adv_parsers.hub2 import process_wohub2
18
20
  from .adv_parsers.humidifier import process_wohumidifier
19
21
  from .adv_parsers.light_strip import process_wostrip
20
22
  from .adv_parsers.lock import process_wolock
@@ -54,7 +56,6 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
54
56
  "modelName": SwitchbotModel.BOT,
55
57
  "modelFriendlyName": "Bot",
56
58
  "func": process_wohand,
57
- "service_uuids": {"cba20d00-224d-11e6-9fb8-0002a5d5c51b"},
58
59
  "manufacturer_id": 89,
59
60
  },
60
61
  "s": {
@@ -100,6 +101,12 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
100
101
  "func": process_wosensorth,
101
102
  "manufacturer_id": 2409,
102
103
  },
104
+ "v": {
105
+ "modelName": SwitchbotModel.HUB2,
106
+ "modelFriendlyName": "Hub 2",
107
+ "func": process_wohub2,
108
+ "manufacturer_id": 2409,
109
+ },
103
110
  "g": {
104
111
  "modelName": SwitchbotModel.PLUG_MINI,
105
112
  "modelFriendlyName": "Plug Mini",
@@ -0,0 +1,40 @@
1
+ """Hub2 parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
9
+ """Process woHub2 sensor manufacturer data."""
10
+ temp_data = None
11
+
12
+ if mfr_data:
13
+ status = mfr_data[12]
14
+ temp_data = mfr_data[13:16]
15
+
16
+ if not temp_data:
17
+ return {}
18
+
19
+ _temp_sign = 1 if temp_data[1] & 0b10000000 else -1
20
+ _temp_c = _temp_sign * (
21
+ (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
22
+ )
23
+ _temp_f = (_temp_c * 9 / 5) + 32
24
+ _temp_f = (_temp_f * 10) / 10
25
+ humidity = temp_data[2] & 0b01111111
26
+ light_level = status & 0b11111
27
+
28
+ if _temp_c == 0 and humidity == 0:
29
+ return {}
30
+
31
+ _wohub2_data = {
32
+ # Data should be flat, but we keep the original structure for now
33
+ "temp": {"c": _temp_c, "f": _temp_f},
34
+ "temperature": _temp_c,
35
+ "fahrenheit": bool(temp_data[2] & 0b10000000),
36
+ "humidity": humidity,
37
+ "lightLevel": light_level,
38
+ }
39
+
40
+ return _wohub2_data
@@ -48,6 +48,7 @@ class SwitchbotModel(StrEnum):
48
48
  CEILING_LIGHT = "WoCeiling"
49
49
  LOCK = "WoLock"
50
50
  BLIND_TILT = "WoBlindTilt"
51
+ HUB2 = "WoHub2"
51
52
 
52
53
 
53
54
  class LockStatus(Enum):
@@ -92,7 +92,12 @@ class SwitchbotLock(SwitchbotDevice):
92
92
  headers: dict = None,
93
93
  ) -> dict:
94
94
  url = f"https://{subdomain}.{SWITCHBOT_APP_API_BASE_URL}/{path}"
95
- async with session.post(url, json=data, headers=headers) as result:
95
+ async with session.post(
96
+ url,
97
+ json=data,
98
+ headers=headers,
99
+ timeout=aiohttp.ClientTimeout(total=10),
100
+ ) as result:
96
101
  if result.status > 299:
97
102
  raise SwitchbotApiError(
98
103
  f"Unexpected status code returned by SwitchBot API: {result.status}"
@@ -42,11 +42,11 @@ class GetSwitchbotDevices:
42
42
 
43
43
  devices = None
44
44
  devices = bleak.BleakScanner(
45
+ detection_callback=self.detection_callback,
45
46
  # TODO: Find new UUIDs to filter on. For example, see
46
47
  # https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/4ad138bb09f0fbbfa41b152ca327a78c1d0b6ba9/devicetypes/meter.md
47
48
  adapter=self._interface,
48
49
  )
49
- devices.register_detection_callback(self.detection_callback)
50
50
 
51
51
  async with CONNECT_LOCK:
52
52
  await devices.start()
@@ -111,7 +111,8 @@ class GetSwitchbotDevices:
111
111
  base_meters = await self._get_devices_by_model("T")
112
112
  plus_meters = await self._get_devices_by_model("i")
113
113
  io_meters = await self._get_devices_by_model("w")
114
- return {**base_meters, **plus_meters, **io_meters}
114
+ hub2_meters = await self._get_devices_by_model("v")
115
+ return {**base_meters, **plus_meters, **io_meters, **hub2_meters}
115
116
 
116
117
  async def get_contactsensors(self) -> dict[str, SwitchBotAdvertisement]:
117
118
  """Return all WoContact/Contact sensor devices with services data."""
@@ -964,6 +964,40 @@ def test_wosensor_active_zero_data():
964
964
  )
965
965
 
966
966
 
967
+ def test_wohub2_passive_and_active():
968
+ """Test parsing wosensor as passive with active data as well."""
969
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
970
+ adv_data = generate_advertisement_data(
971
+ manufacturer_data={
972
+ 2409: b"\xaa\xbb\xcc\xdd\xee\xff\x00\xfffT\x1a\xf1\x82\x07\x9a2\x00"
973
+ },
974
+ service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"v\x00"},
975
+ tx_power=-127,
976
+ rssi=-50,
977
+ )
978
+ result = parse_advertisement_data(ble_device, adv_data)
979
+ assert result == SwitchBotAdvertisement(
980
+ address="aa:bb:cc:dd:ee:ff",
981
+ data={
982
+ "data": {
983
+ "fahrenheit": False,
984
+ "humidity": 50,
985
+ "lightLevel": 2,
986
+ "temp": {"c": 26.7, "f": 80.06},
987
+ "temperature": 26.7,
988
+ },
989
+ "isEncrypted": False,
990
+ "model": "v",
991
+ "modelFriendlyName": "Hub 2",
992
+ "modelName": SwitchbotModel.HUB2,
993
+ "rawAdvData": b"v\x00",
994
+ },
995
+ device=ble_device,
996
+ rssi=-50,
997
+ active=True,
998
+ )
999
+
1000
+
967
1001
  def test_woiosensor_passive_and_active():
968
1002
  """Test parsing woiosensor as passive with active data as well."""
969
1003
  ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
@@ -1286,43 +1320,6 @@ def test_motion_with_light_detected():
1286
1320
  )
1287
1321
 
1288
1322
 
1289
- def test_motion_sensor_motion_passive():
1290
- """Test parsing motion sensor with motion data."""
1291
- ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
1292
- adv_data = generate_advertisement_data(
1293
- manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
1294
- service_data={},
1295
- tx_power=-127,
1296
- rssi=-87,
1297
- )
1298
- result = parse_advertisement_data(
1299
- ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
1300
- )
1301
- assert result == SwitchBotAdvertisement(
1302
- address="aa:bb:cc:dd:ee:ff",
1303
- data={
1304
- "data": {
1305
- "battery": None,
1306
- "iot": None,
1307
- "is_light": False,
1308
- "led": None,
1309
- "light_intensity": None,
1310
- "motion_detected": True,
1311
- "sense_distance": None,
1312
- "tested": None,
1313
- },
1314
- "isEncrypted": False,
1315
- "model": "s",
1316
- "modelFriendlyName": "Motion Sensor",
1317
- "modelName": SwitchbotModel.MOTION_SENSOR,
1318
- "rawAdvData": None,
1319
- },
1320
- device=ble_device,
1321
- rssi=-87,
1322
- active=False,
1323
- )
1324
-
1325
-
1326
1323
  def test_parsing_lock_active():
1327
1324
  """Test parsing lock with active data."""
1328
1325
  ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
File without changes
File without changes
File without changes
File without changes