PySwitchbot 0.56.0__tar.gz → 0.57.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 (66) hide show
  1. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PKG-INFO +1 -1
  2. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/SOURCES.txt +7 -1
  4. pyswitchbot-0.57.0/pyproject.toml +59 -0
  5. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/setup.py +7 -2
  6. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/__init__.py +21 -16
  7. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parser.py +15 -3
  8. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/blind_tilt.py +0 -1
  9. pyswitchbot-0.57.0/switchbot/adv_parsers/humidifier.py +93 -0
  10. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/lock.py +1 -1
  11. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/remote.py +0 -1
  12. pyswitchbot-0.56.0/switchbot/const.py → pyswitchbot-0.57.0/switchbot/const/__init__.py +11 -16
  13. pyswitchbot-0.57.0/switchbot/const/evaporative_humidifier.py +34 -0
  14. pyswitchbot-0.57.0/switchbot/const/lock.py +13 -0
  15. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/base_cover.py +2 -3
  16. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/base_light.py +2 -2
  17. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/curtain.py +0 -1
  18. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/device.py +69 -9
  19. pyswitchbot-0.57.0/switchbot/devices/evaporative_humidifier.py +212 -0
  20. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/lock.py +2 -58
  21. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/relay_switch.py +0 -55
  22. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/discovery.py +6 -1
  23. pyswitchbot-0.57.0/switchbot/helpers.py +17 -0
  24. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_adv_parser.py +27 -1
  25. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_base_cover.py +0 -1
  26. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_blind_tilt.py +0 -1
  27. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_curtain.py +0 -1
  28. pyswitchbot-0.57.0/tests/test_evaporative_humidifier.py +202 -0
  29. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_relay_switch.py +0 -1
  30. pyswitchbot-0.56.0/switchbot/adv_parsers/humidifier.py +0 -33
  31. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/LICENSE +0 -0
  32. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/MANIFEST.in +0 -0
  33. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  34. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/requires.txt +0 -0
  35. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/top_level.txt +0 -0
  36. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/README.md +0 -0
  37. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/setup.cfg +0 -0
  38. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/__init__.py +0 -0
  39. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/bot.py +0 -0
  40. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/bulb.py +0 -0
  41. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  42. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/contact.py +0 -0
  43. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/curtain.py +0 -0
  44. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/hub2.py +0 -0
  45. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/keypad.py +0 -0
  46. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/leak.py +0 -0
  47. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/light_strip.py +0 -0
  48. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/meter.py +0 -0
  49. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/motion.py +0 -0
  50. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/plug.py +0 -0
  51. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/relay_switch.py +0 -0
  52. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/api_config.py +0 -0
  53. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/__init__.py +0 -0
  54. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/blind_tilt.py +0 -0
  55. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/bot.py +0 -0
  56. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/bulb.py +0 -0
  57. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/ceiling_light.py +0 -0
  58. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/contact.py +0 -0
  59. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/humidifier.py +0 -0
  60. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/keypad.py +0 -0
  61. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/light_strip.py +0 -0
  62. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/meter.py +0 -0
  63. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/motion.py +0 -0
  64. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/plug.py +0 -0
  65. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/enum.py +0 -0
  66. {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PySwitchbot
3
- Version: 0.56.0
3
+ Version: 0.57.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.2
2
2
  Name: PySwitchbot
3
- Version: 0.56.0
3
+ Version: 0.57.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,7 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ pyproject.toml
4
5
  setup.py
5
6
  PySwitchbot.egg-info/PKG-INFO
6
7
  PySwitchbot.egg-info/SOURCES.txt
@@ -10,9 +11,9 @@ PySwitchbot.egg-info/top_level.txt
10
11
  switchbot/__init__.py
11
12
  switchbot/adv_parser.py
12
13
  switchbot/api_config.py
13
- switchbot/const.py
14
14
  switchbot/discovery.py
15
15
  switchbot/enum.py
16
+ switchbot/helpers.py
16
17
  switchbot/models.py
17
18
  switchbot/adv_parsers/__init__.py
18
19
  switchbot/adv_parsers/blind_tilt.py
@@ -32,6 +33,9 @@ switchbot/adv_parsers/motion.py
32
33
  switchbot/adv_parsers/plug.py
33
34
  switchbot/adv_parsers/relay_switch.py
34
35
  switchbot/adv_parsers/remote.py
36
+ switchbot/const/__init__.py
37
+ switchbot/const/evaporative_humidifier.py
38
+ switchbot/const/lock.py
35
39
  switchbot/devices/__init__.py
36
40
  switchbot/devices/base_cover.py
37
41
  switchbot/devices/base_light.py
@@ -42,6 +46,7 @@ switchbot/devices/ceiling_light.py
42
46
  switchbot/devices/contact.py
43
47
  switchbot/devices/curtain.py
44
48
  switchbot/devices/device.py
49
+ switchbot/devices/evaporative_humidifier.py
45
50
  switchbot/devices/humidifier.py
46
51
  switchbot/devices/keypad.py
47
52
  switchbot/devices/light_strip.py
@@ -54,4 +59,5 @@ tests/test_adv_parser.py
54
59
  tests/test_base_cover.py
55
60
  tests/test_blind_tilt.py
56
61
  tests/test_curtain.py
62
+ tests/test_evaporative_humidifier.py
57
63
  tests/test_relay_switch.py
@@ -0,0 +1,59 @@
1
+ [tool.ruff]
2
+ target-version = "py311"
3
+ line-length = 88
4
+
5
+ [tool.ruff.lint]
6
+ ignore = [
7
+ "S101", # use of assert
8
+ "D203", # 1 blank line required before class docstring
9
+ "D212", # Multi-line docstring summary should start at the first line
10
+ "D100", # Missing docstring in public module
11
+ "D101", # Missing docstring in public module
12
+ "D102", # Missing docstring in public method
13
+ "D103", # Missing docstring in public module
14
+ "D104", # Missing docstring in public package
15
+ "D105", # Missing docstring in magic method
16
+ "D107", # Missing docstring in `__init__`
17
+ "D400", # First line should end with a period
18
+ "D401", # First line of docstring should be in imperative mood
19
+ "D205", # 1 blank line required between summary line and description
20
+ "D415", # First line should end with a period, question mark, or exclamation point
21
+ "D417", # Missing argument descriptions in the docstring
22
+ "E501", # Line too long
23
+ "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
24
+ "B008", # Do not perform function call
25
+ "S110", # `try`-`except`-`pass` detected, consider logging the exception
26
+ "D106", # Missing docstring in public nested class
27
+ "UP007", # typer needs Optional syntax
28
+ "UP038", # Use `X | Y` in `isinstance` is slower
29
+ "S603", # check for execution of untrusted input
30
+ "S105", # possible hard coded creds
31
+ ]
32
+ select = [
33
+ "B", # flake8-bugbear
34
+ "D", # flake8-docstrings
35
+ "C4", # flake8-comprehensions
36
+ "S", # flake8-bandit
37
+ "F", # pyflake
38
+ "E", # pycodestyle
39
+ "W", # pycodestyle
40
+ "UP", # pyupgrade
41
+ "I", # isort
42
+ "RUF", # ruff specific
43
+ ]
44
+
45
+ [tool.ruff.lint.per-file-ignores]
46
+ "tests/**/*" = [
47
+ "D100",
48
+ "D101",
49
+ "D102",
50
+ "D103",
51
+ "D104",
52
+ "S101",
53
+ ]
54
+ "setup.py" = ["D100"]
55
+ "conftest.py" = ["D100"]
56
+ "docs/conf.py" = ["D100"]
57
+
58
+ [tool.ruff.lint.isort]
59
+ known-first-party = ["pySwitchbot", "tests"]
@@ -7,7 +7,12 @@ long_description = (this_directory / "README.md").read_text()
7
7
 
8
8
  setup(
9
9
  name="PySwitchbot",
10
- packages=["switchbot", "switchbot.devices", "switchbot.adv_parsers"],
10
+ packages=[
11
+ "switchbot",
12
+ "switchbot.devices",
13
+ "switchbot.const",
14
+ "switchbot.adv_parsers",
15
+ ],
11
16
  install_requires=[
12
17
  "aiohttp>=3.9.5",
13
18
  "bleak>=0.19.0",
@@ -15,7 +20,7 @@ setup(
15
20
  "cryptography>=39.0.0",
16
21
  "pyOpenSSL>=23.0.0",
17
22
  ],
18
- version="0.56.0",
23
+ version="0.57.0",
19
24
  description="A library to communicate with Switchbot",
20
25
  long_description=long_description,
21
26
  long_description_content_type="text/markdown",
@@ -16,14 +16,14 @@ from .const import (
16
16
  SwitchbotAuthenticationError,
17
17
  SwitchbotModel,
18
18
  )
19
- from .devices.device import SwitchbotEncryptedDevice
20
19
  from .devices.base_light import SwitchbotBaseLight
21
20
  from .devices.blind_tilt import SwitchbotBlindTilt
22
21
  from .devices.bot import Switchbot
23
22
  from .devices.bulb import SwitchbotBulb
24
23
  from .devices.ceiling_light import SwitchbotCeilingLight
25
24
  from .devices.curtain import SwitchbotCurtain
26
- from .devices.device import ColorMode, SwitchbotDevice
25
+ from .devices.device import ColorMode, SwitchbotDevice, SwitchbotEncryptedDevice
26
+ from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
27
27
  from .devices.humidifier import SwitchbotHumidifier
28
28
  from .devices.light_strip import SwitchbotLightStrip
29
29
  from .devices.lock import SwitchbotLock
@@ -33,30 +33,35 @@ from .discovery import GetSwitchbotDevices
33
33
  from .models import SwitchBotAdvertisement
34
34
 
35
35
  __all__ = [
36
- "get_device",
37
- "close_stale_connections",
38
- "close_stale_connections_by_address",
39
- "parse_advertisement_data",
36
+ "ColorMode",
40
37
  "GetSwitchbotDevices",
38
+ "LockStatus",
41
39
  "SwitchBotAdvertisement",
40
+ "Switchbot",
41
+ "Switchbot",
42
42
  "SwitchbotAccountConnectionError",
43
43
  "SwitchbotApiError",
44
44
  "SwitchbotAuthenticationError",
45
- "SwitchbotEncryptedDevice",
46
- "ColorMode",
47
- "LockStatus",
48
45
  "SwitchbotBaseLight",
46
+ "SwitchbotBlindTilt",
49
47
  "SwitchbotBulb",
50
48
  "SwitchbotCeilingLight",
51
- "SwitchbotDevice",
52
49
  "SwitchbotCurtain",
53
- "SwitchbotLightStrip",
50
+ "SwitchbotDevice",
51
+ "SwitchbotEncryptedDevice",
52
+ "SwitchbotEvaporativeHumidifier",
54
53
  "SwitchbotHumidifier",
55
- "Switchbot",
56
- "SwitchbotPlugMini",
57
- "SwitchbotSupportedType",
58
- "SwitchbotModel",
54
+ "SwitchbotLightStrip",
59
55
  "SwitchbotLock",
60
- "SwitchbotBlindTilt",
56
+ "SwitchbotModel",
57
+ "SwitchbotModel",
58
+ "SwitchbotPlugMini",
59
+ "SwitchbotPlugMini",
61
60
  "SwitchbotRelaySwitch",
61
+ "SwitchbotSupportedType",
62
+ "SwitchbotSupportedType",
63
+ "close_stale_connections",
64
+ "close_stale_connections_by_address",
65
+ "get_device",
66
+ "parse_advertisement_data",
62
67
  ]
@@ -17,7 +17,7 @@ from .adv_parsers.ceiling_light import process_woceiling
17
17
  from .adv_parsers.contact import process_wocontact
18
18
  from .adv_parsers.curtain import process_wocurtain
19
19
  from .adv_parsers.hub2 import process_wohub2
20
- from .adv_parsers.humidifier import process_wohumidifier
20
+ from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohumidifier
21
21
  from .adv_parsers.keypad import process_wokeypad
22
22
  from .adv_parsers.leak import process_leak
23
23
  from .adv_parsers.light_strip import process_wostrip
@@ -41,6 +41,8 @@ SERVICE_DATA_ORDER = (
41
41
  )
42
42
  MFR_DATA_ORDER = (2409, 741, 89)
43
43
 
44
+ APPLE_MANUFACTURER_ID = 76
45
+
44
46
 
45
47
  class SwitchbotSupportedType(TypedDict):
46
48
  """Supported type of Switchbot."""
@@ -162,6 +164,12 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
162
164
  "manufacturer_id": 741,
163
165
  "manufacturer_data_length": 6,
164
166
  },
167
+ "#": {
168
+ "modelName": SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
169
+ "modelFriendlyName": "Evaporative Humidifier",
170
+ "func": process_evaporative_humidifier,
171
+ "manufacturer_id": 2409,
172
+ },
165
173
  "o": {
166
174
  "modelName": SwitchbotModel.LOCK,
167
175
  "modelFriendlyName": "Lock",
@@ -242,10 +250,14 @@ def parse_advertisement_data(
242
250
 
243
251
  _mfr_data = None
244
252
  _mfr_id = None
253
+ manufacturer_data = advertisement_data.manufacturer_data
254
+ if APPLE_MANUFACTURER_ID in manufacturer_data:
255
+ return None
256
+
245
257
  for mfr_id in MFR_DATA_ORDER:
246
- if mfr_id in advertisement_data.manufacturer_data:
258
+ if mfr_id in manufacturer_data:
247
259
  _mfr_id = mfr_id
248
- _mfr_data = advertisement_data.manufacturer_data[mfr_id]
260
+ _mfr_data = manufacturer_data[mfr_id]
249
261
  break
250
262
 
251
263
  if _mfr_data is None and _service_data is None:
@@ -7,7 +7,6 @@ def process_woblindtilt(
7
7
  data: bytes | None, mfr_data: bytes | None, reverse: bool = False
8
8
  ) -> dict[str, bool | int]:
9
9
  """Process woBlindTilt services data."""
10
-
11
10
  if mfr_data is None:
12
11
  return {}
13
12
 
@@ -0,0 +1,93 @@
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
+ OVER_HUMIDIFY_PROTECTION_MODES,
10
+ TARGET_HUMIDITY_MODES,
11
+ HumidifierMode,
12
+ HumidifierWaterLevel,
13
+ )
14
+
15
+ _LOGGER = logging.getLogger(__name__)
16
+
17
+ # mfr_data: 943cc68d3d2e
18
+ # data: 650000cd802b6300
19
+ # data: 650000cd802b6300
20
+ # data: 658000c9802b6300
21
+
22
+
23
+ # Low: 658000c5222b6300
24
+ # Med: 658000c5432b6300
25
+ # High: 658000c5642b6300
26
+ def process_wohumidifier(
27
+ data: bytes | None, mfr_data: bytes | None
28
+ ) -> dict[str, bool | int]:
29
+ """Process WoHumi services data."""
30
+ if data is None:
31
+ return {
32
+ "isOn": None,
33
+ "level": None,
34
+ "switchMode": True,
35
+ }
36
+
37
+ return {
38
+ "isOn": bool(data[1]),
39
+ "level": data[4],
40
+ "switchMode": True,
41
+ }
42
+
43
+
44
+ def process_evaporative_humidifier(
45
+ data: bytes | None, mfr_data: bytes | None
46
+ ) -> dict[str, bool | int]:
47
+ """Process WoHumi services data."""
48
+ if mfr_data is None:
49
+ return {
50
+ "isOn": None,
51
+ "mode": None,
52
+ "target_humidity": None,
53
+ "child_lock": None,
54
+ "over_humidify_protection": None,
55
+ "tank_removed": None,
56
+ "tilted_alert": None,
57
+ "filter_missing": None,
58
+ "humidity": None,
59
+ "temperature": None,
60
+ "filter_run_time": None,
61
+ "filter_alert": None,
62
+ "water_level": None,
63
+ }
64
+
65
+ is_on = bool(mfr_data[7] & 0b10000000)
66
+ mode = HumidifierMode(mfr_data[7] & 0b00001111)
67
+ filter_run_time = timedelta(hours=int.from_bytes(mfr_data[12:14], byteorder="big"))
68
+ has_humidity = bool(mfr_data[9] & 0b10000000)
69
+ has_temperature = bool(mfr_data[10] & 0b10000000)
70
+ is_tank_removed = bool(mfr_data[8] & 0b00000100)
71
+ return {
72
+ "isOn": is_on,
73
+ "mode": mode if is_on else None,
74
+ "target_humidity": (mfr_data[16] & 0b01111111)
75
+ if is_on and mode in TARGET_HUMIDITY_MODES
76
+ else None,
77
+ "child_lock": bool(mfr_data[8] & 0b00100000),
78
+ "over_humidify_protection": bool(mfr_data[8] & 0b10000000)
79
+ if is_on and mode in OVER_HUMIDIFY_PROTECTION_MODES
80
+ else None,
81
+ "tank_removed": is_tank_removed,
82
+ "tilted_alert": bool(mfr_data[8] & 0b00000010),
83
+ "filter_missing": bool(mfr_data[8] & 0b00000001),
84
+ "humidity": (mfr_data[9] & 0b01111111) if has_humidity else None,
85
+ "temperature": float(mfr_data[10] & 0b01111111) + float(mfr_data[11] >> 4) / 10
86
+ if has_temperature
87
+ else None,
88
+ "filter_run_time": filter_run_time,
89
+ "filter_alert": filter_run_time.days >= 10,
90
+ "water_level": HumidifierWaterLevel(mfr_data[11] & 0b00000011)
91
+ if not is_tank_removed
92
+ else None,
93
+ }
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
 
7
- from ..const import LockStatus
7
+ from ..const.lock import LockStatus
8
8
 
9
9
  _LOGGER = logging.getLogger(__name__)
10
10
 
@@ -11,7 +11,6 @@ def process_woremote(
11
11
  data: bytes | None, mfr_data: bytes | None
12
12
  ) -> dict[str, int | None]:
13
13
  """Process WoRemote adv data."""
14
-
15
14
  if data is None:
16
15
  return {
17
16
  "battery": None,
@@ -1,10 +1,11 @@
1
- """Library to handle connection with Switchbot."""
1
+ """Switchbot Device Consts Library."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from enum import Enum
5
+ from ..enum import StrEnum
6
6
 
7
- from .enum import StrEnum
7
+ # Preserve old LockStatus export for backwards compatibility
8
+ from .lock import LockStatus as LockStatus
8
9
 
9
10
  DEFAULT_RETRY_COUNT = 3
10
11
  DEFAULT_RETRY_TIMEOUT = 1
@@ -12,7 +13,8 @@ DEFAULT_SCAN_TIMEOUT = 5
12
13
 
13
14
 
14
15
  class SwitchbotApiError(RuntimeError):
15
- """Raised when API call fails.
16
+ """
17
+ Raised when API call fails.
16
18
 
17
19
  This exception inherits from RuntimeError to avoid breaking existing code
18
20
  but will be changed to Exception in a future release.
@@ -20,7 +22,8 @@ class SwitchbotApiError(RuntimeError):
20
22
 
21
23
 
22
24
  class SwitchbotAuthenticationError(RuntimeError):
23
- """Raised when authentication fails.
25
+ """
26
+ Raised when authentication fails.
24
27
 
25
28
  This exception inherits from RuntimeError to avoid breaking existing code
26
29
  but will be changed to Exception in a future release.
@@ -28,7 +31,8 @@ class SwitchbotAuthenticationError(RuntimeError):
28
31
 
29
32
 
30
33
  class SwitchbotAccountConnectionError(RuntimeError):
31
- """Raised when connection to Switchbot account fails.
34
+ """
35
+ Raised when connection to Switchbot account fails.
32
36
 
33
37
  This exception inherits from RuntimeError to avoid breaking existing code
34
38
  but will be changed to Exception in a future release.
@@ -58,13 +62,4 @@ class SwitchbotModel(StrEnum):
58
62
  RELAY_SWITCH_1PM = "Relay Switch 1PM"
59
63
  RELAY_SWITCH_1 = "Relay Switch 1"
60
64
  REMOTE = "WoRemote"
61
-
62
-
63
- class LockStatus(Enum):
64
- LOCKED = 0
65
- UNLOCKED = 1
66
- LOCKING = 2
67
- UNLOCKING = 3
68
- LOCKING_STOP = 4 # LOCKING_BLOCKED
69
- UNLOCKING_STOP = 5 # UNLOCKING_BLOCKED
70
- NOT_FULLY_LOCKED = 6 # LATCH_LOCKED - Only EU lock type
65
+ EVAPORATIVE_HUMIDIFIER = "Evaporative Humidifier"
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class HumidifierMode(Enum):
7
+ HIGH = 1
8
+ MEDIUM = 2
9
+ LOW = 3
10
+ QUIET = 4
11
+ TARGET_HUMIDITY = 5
12
+ SLEEP = 6
13
+ AUTO = 7
14
+ DRYING_FILTER = 8
15
+
16
+
17
+ class HumidifierWaterLevel(Enum):
18
+ EMPTY = 0
19
+ LOW = 1
20
+ MEDIUM = 2
21
+ HIGH = 3
22
+
23
+
24
+ OVER_HUMIDIFY_PROTECTION_MODES = {
25
+ HumidifierMode.QUIET,
26
+ HumidifierMode.LOW,
27
+ HumidifierMode.MEDIUM,
28
+ HumidifierMode.HIGH,
29
+ }
30
+
31
+ TARGET_HUMIDITY_MODES = {
32
+ HumidifierMode.SLEEP,
33
+ HumidifierMode.TARGET_HUMIDITY,
34
+ }
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class LockStatus(Enum):
7
+ LOCKED = 0
8
+ UNLOCKED = 1
9
+ LOCKING = 2
10
+ UNLOCKING = 3
11
+ LOCKING_STOP = 4 # LOCKING_BLOCKED
12
+ UNLOCKING_STOP = 5 # UNLOCKING_BLOCKED
13
+ NOT_FULLY_LOCKED = 6 # LATCH_LOCKED - Only EU lock type
@@ -33,7 +33,6 @@ class SwitchbotBaseCover(SwitchbotDevice):
33
33
 
34
34
  def __init__(self, reverse: bool, *args: Any, **kwargs: Any) -> None:
35
35
  """Switchbot Cover device constructor."""
36
-
37
36
  super().__init__(*args, **kwargs)
38
37
  self._reverse = reverse
39
38
  self._settings: dict[str, Any] = {}
@@ -43,7 +42,8 @@ class SwitchbotBaseCover(SwitchbotDevice):
43
42
  self._is_closing: bool = False
44
43
 
45
44
  async def _send_multiple_commands(self, keys: list[str]) -> bool:
46
- """Send multiple commands to device.
45
+ """
46
+ Send multiple commands to device.
47
47
 
48
48
  Since we current have no way to tell which command the device
49
49
  needs we send both.
@@ -84,7 +84,6 @@ class SwitchbotBaseCover(SwitchbotDevice):
84
84
 
85
85
  async def get_extended_info_adv(self) -> dict[str, Any] | None:
86
86
  """Get advance page info for device chain."""
87
-
88
87
  _data = await self._send_command(key=COVER_EXT_ADV_KEY)
89
88
  if not _data:
90
89
  _LOGGER.error("%s: Unsuccessful, no result from device", self.name)
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  import logging
5
4
  import time
6
5
  from abc import abstractmethod
7
6
  from typing import Any
8
7
 
8
+ from ..helpers import create_background_task
9
9
  from ..models import SwitchBotAdvertisement
10
10
  from .device import ColorMode, SwitchbotDevice
11
11
 
@@ -106,4 +106,4 @@ class SwitchbotSequenceBaseLight(SwitchbotBaseLight):
106
106
  new_state,
107
107
  )
108
108
  if current_state != new_state:
109
- asyncio.ensure_future(self.update())
109
+ create_background_task(self.update())
@@ -38,7 +38,6 @@ class SwitchbotCurtain(SwitchbotBaseCover):
38
38
 
39
39
  def __init__(self, *args: Any, **kwargs: Any) -> None:
40
40
  """Switchbot Curtain/WoCurtain constructor."""
41
-
42
41
  # The position of the curtain is saved returned with 0 = open and 100 = closed.
43
42
  # This is independent of the calibration of the curtain bot (Open left to right/
44
43
  # Open right to left/Open from the middle).