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.
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PKG-INFO +1 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/SOURCES.txt +7 -1
- pyswitchbot-0.57.0/pyproject.toml +59 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/setup.py +7 -2
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/__init__.py +21 -16
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parser.py +15 -3
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/blind_tilt.py +0 -1
- pyswitchbot-0.57.0/switchbot/adv_parsers/humidifier.py +93 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/lock.py +1 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/remote.py +0 -1
- pyswitchbot-0.56.0/switchbot/const.py → pyswitchbot-0.57.0/switchbot/const/__init__.py +11 -16
- pyswitchbot-0.57.0/switchbot/const/evaporative_humidifier.py +34 -0
- pyswitchbot-0.57.0/switchbot/const/lock.py +13 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/base_cover.py +2 -3
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/base_light.py +2 -2
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/curtain.py +0 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/device.py +69 -9
- pyswitchbot-0.57.0/switchbot/devices/evaporative_humidifier.py +212 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/lock.py +2 -58
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/relay_switch.py +0 -55
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/discovery.py +6 -1
- pyswitchbot-0.57.0/switchbot/helpers.py +17 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_adv_parser.py +27 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_base_cover.py +0 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_blind_tilt.py +0 -1
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_curtain.py +0 -1
- pyswitchbot-0.57.0/tests/test_evaporative_humidifier.py +202 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/tests/test_relay_switch.py +0 -1
- pyswitchbot-0.56.0/switchbot/adv_parsers/humidifier.py +0 -33
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/LICENSE +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/MANIFEST.in +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/README.md +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/setup.cfg +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/hub2.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/keypad.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/leak.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/meter.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/plug.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/adv_parsers/relay_switch.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/enum.py +0 -0
- {pyswitchbot-0.56.0 → pyswitchbot-0.57.0}/switchbot/models.py +0 -0
|
@@ -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=[
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
50
|
+
"SwitchbotDevice",
|
|
51
|
+
"SwitchbotEncryptedDevice",
|
|
52
|
+
"SwitchbotEvaporativeHumidifier",
|
|
54
53
|
"SwitchbotHumidifier",
|
|
55
|
-
"
|
|
56
|
-
"SwitchbotPlugMini",
|
|
57
|
-
"SwitchbotSupportedType",
|
|
58
|
-
"SwitchbotModel",
|
|
54
|
+
"SwitchbotLightStrip",
|
|
59
55
|
"SwitchbotLock",
|
|
60
|
-
"
|
|
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
|
|
258
|
+
if mfr_id in manufacturer_data:
|
|
247
259
|
_mfr_id = mfr_id
|
|
248
|
-
_mfr_data =
|
|
260
|
+
_mfr_data = manufacturer_data[mfr_id]
|
|
249
261
|
break
|
|
250
262
|
|
|
251
263
|
if _mfr_data is None and _service_data is None:
|
|
@@ -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
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Switchbot Device Consts Library."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from enum import
|
|
5
|
+
from ..enum import StrEnum
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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).
|