PySwitchbot 0.64.1__tar.gz → 0.66.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 (104) hide show
  1. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PKG-INFO +1 -1
  2. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PySwitchbot.egg-info/SOURCES.txt +7 -0
  4. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/setup.py +1 -1
  5. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/__init__.py +18 -3
  6. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parser.py +32 -7
  7. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/hub2.py +2 -1
  8. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/hub3.py +2 -1
  9. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/hubmini_matter.py +3 -1
  10. pyswitchbot-0.66.0/switchbot/adv_parsers/humidifier.py +107 -0
  11. pyswitchbot-0.66.0/switchbot/adv_parsers/light_strip.py +32 -0
  12. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/meter.py +3 -1
  13. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/plug.py +3 -1
  14. pyswitchbot-0.66.0/switchbot/adv_parsers/relay_switch.py +48 -0
  15. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/__init__.py +22 -0
  16. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/evaporative_humidifier.py +14 -0
  17. pyswitchbot-0.66.0/switchbot/const/light.py +34 -0
  18. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/base_light.py +25 -11
  19. pyswitchbot-0.66.0/switchbot/devices/bulb.py +143 -0
  20. pyswitchbot-0.66.0/switchbot/devices/ceiling_light.py +105 -0
  21. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/device.py +118 -74
  22. pyswitchbot-0.66.0/switchbot/devices/evaporative_humidifier.py +256 -0
  23. pyswitchbot-0.66.0/switchbot/devices/light_strip.py +259 -0
  24. pyswitchbot-0.66.0/switchbot/devices/relay_switch.py +300 -0
  25. pyswitchbot-0.66.0/switchbot/helpers.py +75 -0
  26. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_adv_parser.py +652 -70
  27. pyswitchbot-0.66.0/tests/test_bulb.py +220 -0
  28. pyswitchbot-0.66.0/tests/test_ceiling_light.py +179 -0
  29. pyswitchbot-0.66.0/tests/test_colormode_imports.py +88 -0
  30. pyswitchbot-0.66.0/tests/test_encrypted_device.py +367 -0
  31. pyswitchbot-0.66.0/tests/test_evaporative_humidifier.py +357 -0
  32. pyswitchbot-0.66.0/tests/test_helpers.py +72 -0
  33. pyswitchbot-0.66.0/tests/test_relay_switch.py +453 -0
  34. pyswitchbot-0.66.0/tests/test_strip_light.py +302 -0
  35. pyswitchbot-0.64.1/switchbot/adv_parsers/humidifier.py +0 -93
  36. pyswitchbot-0.64.1/switchbot/adv_parsers/light_strip.py +0 -21
  37. pyswitchbot-0.64.1/switchbot/adv_parsers/relay_switch.py +0 -32
  38. pyswitchbot-0.64.1/switchbot/devices/bulb.py +0 -94
  39. pyswitchbot-0.64.1/switchbot/devices/ceiling_light.py +0 -69
  40. pyswitchbot-0.64.1/switchbot/devices/evaporative_humidifier.py +0 -212
  41. pyswitchbot-0.64.1/switchbot/devices/light_strip.py +0 -84
  42. pyswitchbot-0.64.1/switchbot/devices/relay_switch.py +0 -136
  43. pyswitchbot-0.64.1/switchbot/helpers.py +0 -17
  44. pyswitchbot-0.64.1/tests/test_evaporative_humidifier.py +0 -202
  45. pyswitchbot-0.64.1/tests/test_relay_switch.py +0 -73
  46. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/LICENSE +0 -0
  47. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/MANIFEST.in +0 -0
  48. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  49. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PySwitchbot.egg-info/requires.txt +0 -0
  50. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/PySwitchbot.egg-info/top_level.txt +0 -0
  51. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/README.md +0 -0
  52. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/pyproject.toml +0 -0
  53. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/setup.cfg +0 -0
  54. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/__init__.py +0 -0
  55. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/air_purifier.py +0 -0
  56. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
  57. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/bot.py +0 -0
  58. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/bulb.py +0 -0
  59. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  60. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/contact.py +0 -0
  61. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/curtain.py +0 -0
  62. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/fan.py +0 -0
  63. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/keypad.py +0 -0
  64. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/leak.py +0 -0
  65. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/lock.py +0 -0
  66. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/motion.py +0 -0
  67. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/remote.py +0 -0
  68. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/roller_shade.py +0 -0
  69. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/adv_parsers/vacuum.py +0 -0
  70. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/api_config.py +0 -0
  71. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/air_purifier.py +0 -0
  72. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/fan.py +0 -0
  73. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/hub2.py +0 -0
  74. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/hub3.py +0 -0
  75. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/const/lock.py +0 -0
  76. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/__init__.py +0 -0
  77. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/air_purifier.py +0 -0
  78. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/base_cover.py +0 -0
  79. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/blind_tilt.py +0 -0
  80. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/bot.py +0 -0
  81. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/contact.py +0 -0
  82. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/curtain.py +0 -0
  83. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/fan.py +0 -0
  84. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/humidifier.py +0 -0
  85. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/keypad.py +0 -0
  86. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/lock.py +0 -0
  87. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/meter.py +0 -0
  88. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/motion.py +0 -0
  89. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/plug.py +0 -0
  90. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/roller_shade.py +0 -0
  91. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/devices/vacuum.py +0 -0
  92. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/discovery.py +0 -0
  93. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/enum.py +0 -0
  94. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/switchbot/models.py +0 -0
  95. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_air_purifier.py +0 -0
  96. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_base_cover.py +0 -0
  97. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_blind_tilt.py +0 -0
  98. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_curtain.py +0 -0
  99. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_fan.py +0 -0
  100. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_hub2.py +0 -0
  101. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_hub3.py +0 -0
  102. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_lock.py +0 -0
  103. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_roller_shade.py +0 -0
  104. {pyswitchbot-0.64.1 → pyswitchbot-0.66.0}/tests/test_vacuum.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.64.1
3
+ Version: 0.66.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.64.1
3
+ Version: 0.66.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -45,6 +45,7 @@ switchbot/const/evaporative_humidifier.py
45
45
  switchbot/const/fan.py
46
46
  switchbot/const/hub2.py
47
47
  switchbot/const/hub3.py
48
+ switchbot/const/light.py
48
49
  switchbot/const/lock.py
49
50
  switchbot/devices/__init__.py
50
51
  switchbot/devices/air_purifier.py
@@ -73,12 +74,18 @@ tests/test_adv_parser.py
73
74
  tests/test_air_purifier.py
74
75
  tests/test_base_cover.py
75
76
  tests/test_blind_tilt.py
77
+ tests/test_bulb.py
78
+ tests/test_ceiling_light.py
79
+ tests/test_colormode_imports.py
76
80
  tests/test_curtain.py
81
+ tests/test_encrypted_device.py
77
82
  tests/test_evaporative_humidifier.py
78
83
  tests/test_fan.py
84
+ tests/test_helpers.py
79
85
  tests/test_hub2.py
80
86
  tests/test_hub3.py
81
87
  tests/test_lock.py
82
88
  tests/test_relay_switch.py
83
89
  tests/test_roller_shade.py
90
+ tests/test_strip_light.py
84
91
  tests/test_vacuum.py
@@ -20,7 +20,7 @@ setup(
20
20
  "cryptography>=39.0.0",
21
21
  "pyOpenSSL>=23.0.0",
22
22
  ],
23
- version="0.64.1",
23
+ version="0.66.0",
24
24
  description="A library to communicate with Switchbot",
25
25
  long_description=long_description,
26
26
  long_description_content_type="text/markdown",
@@ -11,8 +11,15 @@ from bleak_retry_connector import (
11
11
  from .adv_parser import SwitchbotSupportedType, parse_advertisement_data
12
12
  from .const import (
13
13
  AirPurifierMode,
14
+ BulbColorMode,
15
+ CeilingLightColorMode,
16
+ ColorMode,
14
17
  FanMode,
18
+ HumidifierAction,
19
+ HumidifierMode,
20
+ HumidifierWaterLevel,
15
21
  LockStatus,
22
+ StripLightColorMode,
16
23
  SwitchbotAccountConnectionError,
17
24
  SwitchbotApiError,
18
25
  SwitchbotAuthenticationError,
@@ -25,14 +32,14 @@ from .devices.bot import Switchbot
25
32
  from .devices.bulb import SwitchbotBulb
26
33
  from .devices.ceiling_light import SwitchbotCeilingLight
27
34
  from .devices.curtain import SwitchbotCurtain
28
- from .devices.device import ColorMode, SwitchbotDevice, SwitchbotEncryptedDevice
35
+ from .devices.device import SwitchbotDevice, SwitchbotEncryptedDevice
29
36
  from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
30
37
  from .devices.fan import SwitchbotFan
31
38
  from .devices.humidifier import SwitchbotHumidifier
32
- from .devices.light_strip import SwitchbotLightStrip
39
+ from .devices.light_strip import SwitchbotLightStrip, SwitchbotStripLight3
33
40
  from .devices.lock import SwitchbotLock
34
41
  from .devices.plug import SwitchbotPlugMini
35
- from .devices.relay_switch import SwitchbotRelaySwitch
42
+ from .devices.relay_switch import SwitchbotRelaySwitch, SwitchbotRelaySwitch2PM
36
43
  from .devices.roller_shade import SwitchbotRollerShade
37
44
  from .devices.vacuum import SwitchbotVacuum
38
45
  from .discovery import GetSwitchbotDevices
@@ -40,10 +47,16 @@ from .models import SwitchBotAdvertisement
40
47
 
41
48
  __all__ = [
42
49
  "AirPurifierMode",
50
+ "BulbColorMode",
51
+ "CeilingLightColorMode",
43
52
  "ColorMode",
44
53
  "FanMode",
45
54
  "GetSwitchbotDevices",
55
+ "HumidifierAction",
56
+ "HumidifierMode",
57
+ "HumidifierWaterLevel",
46
58
  "LockStatus",
59
+ "StripLightColorMode",
47
60
  "SwitchBotAdvertisement",
48
61
  "Switchbot",
49
62
  "Switchbot",
@@ -68,7 +81,9 @@ __all__ = [
68
81
  "SwitchbotPlugMini",
69
82
  "SwitchbotPlugMini",
70
83
  "SwitchbotRelaySwitch",
84
+ "SwitchbotRelaySwitch2PM",
71
85
  "SwitchbotRollerShade",
86
+ "SwitchbotStripLight3",
72
87
  "SwitchbotSupportedType",
73
88
  "SwitchbotSupportedType",
74
89
  "SwitchbotVacuum",
@@ -24,14 +24,15 @@ from .adv_parsers.hubmini_matter import process_hubmini_matter
24
24
  from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohumidifier
25
25
  from .adv_parsers.keypad import process_wokeypad
26
26
  from .adv_parsers.leak import process_leak
27
- from .adv_parsers.light_strip import process_wostrip
27
+ from .adv_parsers.light_strip import process_light, process_wostrip
28
28
  from .adv_parsers.lock import process_lock2, process_wolock, process_wolock_pro
29
29
  from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
30
30
  from .adv_parsers.motion import process_wopresence
31
31
  from .adv_parsers.plug import process_woplugmini
32
32
  from .adv_parsers.relay_switch import (
33
- process_worelay_switch_1,
34
- process_worelay_switch_1pm,
33
+ process_garage_door_opener,
34
+ process_relay_switch_2pm,
35
+ process_relay_switch_common_data,
35
36
  )
36
37
  from .adv_parsers.remote import process_woremote
37
38
  from .adv_parsers.roller_shade import process_worollershade
@@ -115,13 +116,13 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
115
116
  },
116
117
  "4": {
117
118
  "modelName": SwitchbotModel.METER_PRO,
118
- "modelFriendlyName": "Meter",
119
+ "modelFriendlyName": "Meter Pro",
119
120
  "func": process_wosensorth,
120
121
  "manufacturer_id": 2409,
121
122
  },
122
123
  "5": {
123
124
  "modelName": SwitchbotModel.METER_PRO_C,
124
- "modelFriendlyName": "Meter",
125
+ "modelFriendlyName": "Meter Pro CO2",
125
126
  "func": process_wosensorth_c,
126
127
  "manufacturer_id": 2409,
127
128
  },
@@ -207,13 +208,13 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
207
208
  "<": {
208
209
  "modelName": SwitchbotModel.RELAY_SWITCH_1PM,
209
210
  "modelFriendlyName": "Relay Switch 1PM",
210
- "func": process_worelay_switch_1pm,
211
+ "func": process_relay_switch_common_data,
211
212
  "manufacturer_id": 2409,
212
213
  },
213
214
  ";": {
214
215
  "modelName": SwitchbotModel.RELAY_SWITCH_1,
215
216
  "modelFriendlyName": "Relay Switch 1",
216
- "func": process_worelay_switch_1,
217
+ "func": process_relay_switch_common_data,
217
218
  "manufacturer_id": 2409,
218
219
  },
219
220
  "b": {
@@ -312,6 +313,30 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
312
313
  "func": process_lock2,
313
314
  "manufacturer_id": 2409,
314
315
  },
316
+ ">": {
317
+ "modelName": SwitchbotModel.GARAGE_DOOR_OPENER,
318
+ "modelFriendlyName": "Garage Door Opener",
319
+ "func": process_garage_door_opener,
320
+ "manufacturer_id": 2409,
321
+ },
322
+ "=": {
323
+ "modelName": SwitchbotModel.RELAY_SWITCH_2PM,
324
+ "modelFriendlyName": "Relay Switch 2PM",
325
+ "func": process_relay_switch_2pm,
326
+ "manufacturer_id": 2409,
327
+ },
328
+ b"\x00\x10\xd0\xb0": {
329
+ "modelName": SwitchbotModel.FLOOR_LAMP,
330
+ "modelFriendlyName": "Floor Lamp",
331
+ "func": process_light,
332
+ "manufacturer_id": 2409,
333
+ },
334
+ b"\x00\x10\xd0\xb1": {
335
+ "modelName": SwitchbotModel.STRIP_LIGHT_3,
336
+ "modelFriendlyName": "Strip Light 3",
337
+ "func": process_light,
338
+ "manufacturer_id": 2409,
339
+ },
315
340
  }
316
341
 
317
342
  _SWITCHBOT_MODEL_TO_CHAR = {
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import Any
6
6
 
7
7
  from ..const.hub2 import LIGHT_INTENSITY_MAP
8
+ from ..helpers import celsius_to_fahrenheit
8
9
 
9
10
 
10
11
  def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
@@ -22,7 +23,7 @@ def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]
22
23
  _temp_c = _temp_sign * (
23
24
  (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
24
25
  )
25
- _temp_f = (_temp_c * 9 / 5) + 32
26
+ _temp_f = celsius_to_fahrenheit(_temp_c)
26
27
  _temp_f = (_temp_f * 10) / 10
27
28
  humidity = temp_data[2] & 0b01111111
28
29
  light_level = status & 0b11111
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import Any
6
6
 
7
7
  from ..const.hub3 import LIGHT_INTENSITY_MAP
8
+ from ..helpers import celsius_to_fahrenheit
8
9
 
9
10
 
10
11
  def process_hub3(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
@@ -26,7 +27,7 @@ def process_hub3(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
26
27
  _temp_c = _temp_sign * (
27
28
  (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
28
29
  )
29
- _temp_f = round(((_temp_c * 9 / 5) + 32), 1)
30
+ _temp_f = round(celsius_to_fahrenheit(_temp_c), 1)
30
31
  humidity = temp_data[2] & 0b01111111
31
32
  motion_detected = bool(device_data[10] & 0b10000000)
32
33
 
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any
6
6
 
7
+ from ..helpers import celsius_to_fahrenheit
8
+
7
9
 
8
10
  def process_hubmini_matter(
9
11
  data: bytes | None, mfr_data: bytes | None
@@ -21,7 +23,7 @@ def process_hubmini_matter(
21
23
  _temp_c = _temp_sign * (
22
24
  (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
23
25
  )
24
- _temp_f = (_temp_c * 9 / 5) + 32
26
+ _temp_f = celsius_to_fahrenheit(_temp_c)
25
27
  _temp_f = (_temp_f * 10) / 10
26
28
  humidity = temp_data[2] & 0b01111111
27
29
 
@@ -0,0 +1,107 @@
1
+ """Humidifier adv parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import timedelta
7
+
8
+ from ..const.evaporative_humidifier import (
9
+ HumidifierMode,
10
+ HumidifierWaterLevel,
11
+ )
12
+ from ..helpers import celsius_to_fahrenheit
13
+
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+ # mfr_data: 943cc68d3d2e
17
+ # data: 650000cd802b6300
18
+ # data: 650000cd802b6300
19
+ # data: 658000c9802b6300
20
+
21
+
22
+ # Low: 658000c5222b6300
23
+ # Med: 658000c5432b6300
24
+ # High: 658000c5642b6300
25
+
26
+
27
+ def calculate_temperature_and_humidity(
28
+ data: bytes, is_meter_binded: bool = True
29
+ ) -> tuple[float | None, float | None, int | None]:
30
+ """Calculate temperature and humidity based on the given flag."""
31
+ if len(data) < 3 or not is_meter_binded:
32
+ return None, None, None
33
+
34
+ humidity = data[0] & 0b01111111
35
+ if humidity > 100:
36
+ return None, None, None
37
+
38
+ _temp_sign = 1 if data[1] & 0b10000000 else -1
39
+ _temp_c = _temp_sign * ((data[1] & 0b01111111) + ((data[2] >> 4) / 10))
40
+ _temp_f = celsius_to_fahrenheit(_temp_c)
41
+
42
+ return _temp_c, _temp_f, humidity
43
+
44
+
45
+ def process_wohumidifier(
46
+ data: bytes | None, mfr_data: bytes | None
47
+ ) -> dict[str, bool | int]:
48
+ """Process WoHumi services data."""
49
+ if data is None:
50
+ return {
51
+ "isOn": None,
52
+ "level": None,
53
+ "switchMode": True,
54
+ }
55
+
56
+ return {
57
+ "isOn": bool(data[1]),
58
+ "level": data[4],
59
+ "switchMode": True,
60
+ }
61
+
62
+
63
+ def process_evaporative_humidifier(
64
+ data: bytes | None, mfr_data: bytes | None
65
+ ) -> dict[str, bool | int]:
66
+ """Process WoHumi services data."""
67
+ if mfr_data is None:
68
+ return {}
69
+
70
+ seq_number = mfr_data[6]
71
+ is_on = bool(mfr_data[7] & 0b10000000)
72
+ mode = HumidifierMode(mfr_data[7] & 0b00001111)
73
+ over_humidify_protection = bool(mfr_data[8] & 0b10000000)
74
+ child_lock = bool(mfr_data[8] & 0b00100000)
75
+ tank_removed = bool(mfr_data[8] & 0b00000100)
76
+ tilted_alert = bool(mfr_data[8] & 0b00000010)
77
+ filter_missing = bool(mfr_data[8] & 0b00000001)
78
+ is_meter_binded = bool(mfr_data[9] & 0b10000000)
79
+
80
+ _temp_c, _temp_f, humidity = calculate_temperature_and_humidity(
81
+ mfr_data[9:12], is_meter_binded
82
+ )
83
+
84
+ water_level = HumidifierWaterLevel(mfr_data[11] & 0b00000011).name.lower()
85
+ filter_run_time = timedelta(
86
+ hours=int.from_bytes(mfr_data[12:14], byteorder="big") & 0xFFF
87
+ )
88
+ target_humidity = mfr_data[16] & 0b01111111
89
+
90
+ return {
91
+ "seq_number": seq_number,
92
+ "isOn": is_on,
93
+ "mode": mode,
94
+ "over_humidify_protection": over_humidify_protection,
95
+ "child_lock": child_lock,
96
+ "tank_removed": tank_removed,
97
+ "tilted_alert": tilted_alert,
98
+ "filter_missing": filter_missing,
99
+ "is_meter_binded": is_meter_binded,
100
+ "humidity": humidity,
101
+ "temperature": _temp_c,
102
+ "temp": {"c": _temp_c, "f": _temp_f},
103
+ "water_level": water_level,
104
+ "filter_run_time": filter_run_time,
105
+ "filter_alert": filter_run_time.days >= 10,
106
+ "target_humidity": target_humidity,
107
+ }
@@ -0,0 +1,32 @@
1
+ """Light strip adv parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+
7
+
8
+ def process_wostrip(
9
+ data: bytes | None, mfr_data: bytes | None
10
+ ) -> dict[str, bool | int]:
11
+ """Process WoStrip services data."""
12
+ if mfr_data is None:
13
+ return {}
14
+ return {
15
+ "sequence_number": mfr_data[6],
16
+ "isOn": bool(mfr_data[7] & 0b10000000),
17
+ "brightness": mfr_data[7] & 0b01111111,
18
+ "delay": bool(mfr_data[8] & 0b10000000),
19
+ "network_state": (mfr_data[8] & 0b01110000) >> 4,
20
+ "color_mode": mfr_data[8] & 0b00001111,
21
+ }
22
+
23
+
24
+ def process_light(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
25
+ """Support for strip light 3 and floor lamp."""
26
+ common_data = process_wostrip(data, mfr_data)
27
+ if not common_data:
28
+ return {}
29
+
30
+ light_data = {"cw": struct.unpack(">H", mfr_data[16:18])[0]}
31
+
32
+ return common_data | light_data
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  import struct
6
6
  from typing import Any
7
7
 
8
+ from ..helpers import celsius_to_fahrenheit
9
+
8
10
  CO2_UNPACK = struct.Struct(">H").unpack_from
9
11
 
10
12
 
@@ -28,7 +30,7 @@ def process_wosensorth(data: bytes | None, mfr_data: bytes | None) -> dict[str,
28
30
  _temp_c = _temp_sign * (
29
31
  (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
30
32
  )
31
- _temp_f = (_temp_c * 9 / 5) + 32
33
+ _temp_f = celsius_to_fahrenheit(_temp_c)
32
34
  _temp_f = (_temp_f * 10) / 10
33
35
  humidity = temp_data[2] & 0b01111111
34
36
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from ..helpers import parse_power_data
6
+
5
7
 
6
8
  def process_woplugmini(
7
9
  data: bytes | None, mfr_data: bytes | None
@@ -13,5 +15,5 @@ def process_woplugmini(
13
15
  "switchMode": True,
14
16
  "isOn": mfr_data[7] == 0x80,
15
17
  "wifi_rssi": -mfr_data[9],
16
- "power": (((mfr_data[10] << 8) + mfr_data[11]) & 0x7FFF) / 10, # W
18
+ "power": parse_power_data(mfr_data, 10, 10.0, 0x7FFF), # W
17
19
  }
@@ -0,0 +1,48 @@
1
+ """Relay Switch adv parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def process_relay_switch_common_data(
9
+ data: bytes | None, mfr_data: bytes | None
10
+ ) -> dict[str, Any]:
11
+ """Process relay switch 1 and 1PM common data."""
12
+ if mfr_data is None:
13
+ return {}
14
+ return {
15
+ "switchMode": True, # for compatibility, useless
16
+ "sequence_number": mfr_data[6],
17
+ "isOn": bool(mfr_data[7] & 0b10000000),
18
+ }
19
+
20
+
21
+ def process_garage_door_opener(
22
+ data: bytes | None, mfr_data: bytes | None
23
+ ) -> dict[str, Any]:
24
+ """Process garage door opener services data."""
25
+ if mfr_data is None:
26
+ return {}
27
+ common_data = process_relay_switch_common_data(data, mfr_data)
28
+ common_data["door_open"] = not bool(mfr_data[7] & 0b00100000)
29
+ return common_data
30
+
31
+
32
+ def process_relay_switch_2pm(
33
+ data: bytes | None, mfr_data: bytes | None
34
+ ) -> dict[int, dict[str, Any]]:
35
+ """Process Relay Switch 2PM services data."""
36
+ if mfr_data is None:
37
+ return {}
38
+
39
+ return {
40
+ 1: {
41
+ **process_relay_switch_common_data(data, mfr_data),
42
+ },
43
+ 2: {
44
+ "switchMode": True, # for compatibility, useless
45
+ "sequence_number": mfr_data[6],
46
+ "isOn": bool(mfr_data[7] & 0b01000000),
47
+ },
48
+ }
@@ -4,7 +4,18 @@ from __future__ import annotations
4
4
 
5
5
  from ..enum import StrEnum
6
6
  from .air_purifier import AirPurifierMode
7
+ from .evaporative_humidifier import (
8
+ HumidifierAction,
9
+ HumidifierMode,
10
+ HumidifierWaterLevel,
11
+ )
7
12
  from .fan import FanMode
13
+ from .light import (
14
+ BulbColorMode,
15
+ CeilingLightColorMode,
16
+ ColorMode,
17
+ StripLightColorMode,
18
+ )
8
19
 
9
20
  # Preserve old LockStatus export for backwards compatibility
10
21
  from .lock import LockStatus
@@ -78,6 +89,10 @@ class SwitchbotModel(StrEnum):
78
89
  HUB3 = "Hub3"
79
90
  LOCK_ULTRA = "Lock Ultra"
80
91
  LOCK_LITE = "Lock Lite"
92
+ GARAGE_DOOR_OPENER = "Garage Door Opener"
93
+ RELAY_SWITCH_2PM = "Relay Switch 2PM"
94
+ STRIP_LIGHT_3 = "Strip Light 3"
95
+ FLOOR_LAMP = "Floor Lamp"
81
96
 
82
97
 
83
98
  __all__ = [
@@ -85,8 +100,15 @@ __all__ = [
85
100
  "DEFAULT_RETRY_TIMEOUT",
86
101
  "DEFAULT_SCAN_TIMEOUT",
87
102
  "AirPurifierMode",
103
+ "BulbColorMode",
104
+ "CeilingLightColorMode",
105
+ "ColorMode",
88
106
  "FanMode",
107
+ "HumidifierAction",
108
+ "HumidifierMode",
109
+ "HumidifierWaterLevel",
89
110
  "LockStatus",
111
+ "StripLightColorMode",
90
112
  "SwitchbotAccountConnectionError",
91
113
  "SwitchbotApiError",
92
114
  "SwitchbotAuthenticationError",
@@ -13,6 +13,10 @@ class HumidifierMode(Enum):
13
13
  AUTO = 7
14
14
  DRYING_FILTER = 8
15
15
 
16
+ @classmethod
17
+ def get_modes(cls) -> list[str]:
18
+ return [mode.name.lower() for mode in cls]
19
+
16
20
 
17
21
  class HumidifierWaterLevel(Enum):
18
22
  EMPTY = 0
@@ -20,6 +24,16 @@ class HumidifierWaterLevel(Enum):
20
24
  MEDIUM = 2
21
25
  HIGH = 3
22
26
 
27
+ @classmethod
28
+ def get_levels(cls) -> list[str]:
29
+ return [level.name.lower() for level in cls]
30
+
31
+
32
+ class HumidifierAction(Enum):
33
+ OFF = 0
34
+ HUMIDIFYING = 1
35
+ DRYING = 2
36
+
23
37
 
24
38
  OVER_HUMIDIFY_PROTECTION_MODES = {
25
39
  HumidifierMode.QUIET,
@@ -0,0 +1,34 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ColorMode(Enum):
5
+ OFF = 0
6
+ COLOR_TEMP = 1
7
+ RGB = 2
8
+ EFFECT = 3
9
+
10
+
11
+ class StripLightColorMode(Enum):
12
+ RGB = 2
13
+ SCENE = 3
14
+ MUSIC = 4
15
+ CONTROLLER = 5
16
+ COLOR_TEMP = 6
17
+ UNKNOWN = 10
18
+
19
+
20
+ class BulbColorMode(Enum):
21
+ COLOR_TEMP = 1
22
+ RGB = 2
23
+ DYNAMIC = 3
24
+ UNKNOWN = 10
25
+
26
+
27
+ class CeilingLightColorMode(Enum):
28
+ COLOR_TEMP = 0
29
+ NIGHT = 1
30
+ MUSIC = 4
31
+ UNKNOWN = 10
32
+
33
+
34
+ DEFAULT_COLOR_TEMP = 4001
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import time
5
4
  from abc import abstractmethod
6
5
  from typing import Any
7
6
 
8
7
  from ..helpers import create_background_task
9
8
  from ..models import SwitchBotAdvertisement
10
- from .device import ColorMode, SwitchbotDevice
9
+ from .device import SwitchbotDevice
11
10
 
12
11
  _LOGGER = logging.getLogger(__name__)
13
12
 
@@ -43,9 +42,10 @@ class SwitchbotBaseLight(SwitchbotDevice):
43
42
  return self._get_adv_value("brightness") or 0
44
43
 
45
44
  @property
46
- def color_mode(self) -> ColorMode:
45
+ @abstractmethod
46
+ def color_mode(self) -> Any:
47
47
  """Return the current color mode."""
48
- return ColorMode(self._get_adv_value("color_mode") or 0)
48
+ raise NotImplementedError("Subclasses must implement color mode")
49
49
 
50
50
  @property
51
51
  def min_temp(self) -> int:
@@ -57,10 +57,19 @@ class SwitchbotBaseLight(SwitchbotDevice):
57
57
  """Return maximum color temp."""
58
58
  return 6500
59
59
 
60
+ @property
61
+ def get_effect_list(self) -> list[str] | None:
62
+ """Return the list of supported effects."""
63
+ return None
64
+
60
65
  def is_on(self) -> bool | None:
61
66
  """Return bulb state from cache."""
62
67
  return self._get_adv_value("isOn")
63
68
 
69
+ def get_effect(self):
70
+ """Return the current effect."""
71
+ return self._get_adv_value("effect")
72
+
64
73
  @abstractmethod
65
74
  async def turn_on(self) -> bool:
66
75
  """Turn device on."""
@@ -81,13 +90,18 @@ class SwitchbotBaseLight(SwitchbotDevice):
81
90
  async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
82
91
  """Set rgb."""
83
92
 
84
- def poll_needed(self, last_poll_time: float | None) -> bool:
85
- """Return if poll is needed."""
86
- return False
87
-
88
- async def update(self) -> None:
89
- """Update device data."""
90
- self._last_full_update = time.monotonic()
93
+ async def _send_multiple_commands(self, keys: list[str]) -> bool:
94
+ """
95
+ Send multiple commands to device.
96
+
97
+ Since we current have no way to tell which command the device
98
+ needs we send both.
99
+ """
100
+ final_result = False
101
+ for key in keys:
102
+ result = await self._send_command(key)
103
+ final_result |= self._check_command_result(result, 0, {1})
104
+ return final_result
91
105
 
92
106
 
93
107
  class SwitchbotSequenceBaseLight(SwitchbotBaseLight):