PySwitchbot 0.64.0__tar.gz → 0.65.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.64.0 → pyswitchbot-0.65.0}/PKG-INFO +1 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/PySwitchbot.egg-info/SOURCES.txt +1 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/setup.py +1 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/__init__.py +8 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parser.py +19 -6
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/blind_tilt.py +1 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/hub2.py +2 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/hub3.py +2 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/hubmini_matter.py +3 -1
- pyswitchbot-0.65.0/switchbot/adv_parsers/humidifier.py +107 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/meter.py +3 -1
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/plug.py +3 -1
- pyswitchbot-0.65.0/switchbot/adv_parsers/relay_switch.py +48 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/__init__.py +10 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/evaporative_humidifier.py +14 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/device.py +10 -6
- pyswitchbot-0.65.0/switchbot/devices/evaporative_humidifier.py +256 -0
- pyswitchbot-0.65.0/switchbot/devices/relay_switch.py +300 -0
- pyswitchbot-0.65.0/switchbot/helpers.py +75 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_adv_parser.py +556 -70
- pyswitchbot-0.65.0/tests/test_evaporative_humidifier.py +357 -0
- pyswitchbot-0.65.0/tests/test_helpers.py +72 -0
- pyswitchbot-0.65.0/tests/test_relay_switch.py +453 -0
- pyswitchbot-0.64.0/switchbot/adv_parsers/humidifier.py +0 -93
- pyswitchbot-0.64.0/switchbot/adv_parsers/relay_switch.py +0 -32
- pyswitchbot-0.64.0/switchbot/devices/evaporative_humidifier.py +0 -212
- pyswitchbot-0.64.0/switchbot/devices/relay_switch.py +0 -136
- pyswitchbot-0.64.0/switchbot/helpers.py +0 -17
- pyswitchbot-0.64.0/tests/test_evaporative_humidifier.py +0 -202
- pyswitchbot-0.64.0/tests/test_relay_switch.py +0 -73
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/LICENSE +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/MANIFEST.in +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/README.md +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/pyproject.toml +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/setup.cfg +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/air_purifier.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/fan.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/keypad.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/leak.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/lock.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/remote.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/roller_shade.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/adv_parsers/vacuum.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/air_purifier.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/fan.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/hub2.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/hub3.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/const/lock.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/air_purifier.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/base_cover.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/fan.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/lock.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/roller_shade.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/devices/vacuum.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/discovery.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/enum.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/switchbot/models.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_air_purifier.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_base_cover.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_blind_tilt.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_curtain.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_fan.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_hub2.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_hub3.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_lock.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_roller_shade.py +0 -0
- {pyswitchbot-0.64.0 → pyswitchbot-0.65.0}/tests/test_vacuum.py +0 -0
|
@@ -12,6 +12,9 @@ from .adv_parser import SwitchbotSupportedType, parse_advertisement_data
|
|
|
12
12
|
from .const import (
|
|
13
13
|
AirPurifierMode,
|
|
14
14
|
FanMode,
|
|
15
|
+
HumidifierAction,
|
|
16
|
+
HumidifierMode,
|
|
17
|
+
HumidifierWaterLevel,
|
|
15
18
|
LockStatus,
|
|
16
19
|
SwitchbotAccountConnectionError,
|
|
17
20
|
SwitchbotApiError,
|
|
@@ -32,7 +35,7 @@ from .devices.humidifier import SwitchbotHumidifier
|
|
|
32
35
|
from .devices.light_strip import SwitchbotLightStrip
|
|
33
36
|
from .devices.lock import SwitchbotLock
|
|
34
37
|
from .devices.plug import SwitchbotPlugMini
|
|
35
|
-
from .devices.relay_switch import SwitchbotRelaySwitch
|
|
38
|
+
from .devices.relay_switch import SwitchbotRelaySwitch, SwitchbotRelaySwitch2PM
|
|
36
39
|
from .devices.roller_shade import SwitchbotRollerShade
|
|
37
40
|
from .devices.vacuum import SwitchbotVacuum
|
|
38
41
|
from .discovery import GetSwitchbotDevices
|
|
@@ -43,6 +46,9 @@ __all__ = [
|
|
|
43
46
|
"ColorMode",
|
|
44
47
|
"FanMode",
|
|
45
48
|
"GetSwitchbotDevices",
|
|
49
|
+
"HumidifierAction",
|
|
50
|
+
"HumidifierMode",
|
|
51
|
+
"HumidifierWaterLevel",
|
|
46
52
|
"LockStatus",
|
|
47
53
|
"SwitchBotAdvertisement",
|
|
48
54
|
"Switchbot",
|
|
@@ -68,6 +74,7 @@ __all__ = [
|
|
|
68
74
|
"SwitchbotPlugMini",
|
|
69
75
|
"SwitchbotPlugMini",
|
|
70
76
|
"SwitchbotRelaySwitch",
|
|
77
|
+
"SwitchbotRelaySwitch2PM",
|
|
71
78
|
"SwitchbotRollerShade",
|
|
72
79
|
"SwitchbotSupportedType",
|
|
73
80
|
"SwitchbotSupportedType",
|
|
@@ -30,8 +30,9 @@ 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
|
-
|
|
34
|
-
|
|
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":
|
|
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":
|
|
217
|
+
"func": process_relay_switch_common_data,
|
|
217
218
|
"manufacturer_id": 2409,
|
|
218
219
|
},
|
|
219
220
|
"b": {
|
|
@@ -312,6 +313,18 @@ 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
|
+
},
|
|
315
328
|
}
|
|
316
329
|
|
|
317
330
|
_SWITCHBOT_MODEL_TO_CHAR = {
|
|
@@ -14,7 +14,7 @@ def process_woblindtilt(
|
|
|
14
14
|
|
|
15
15
|
_tilt = max(min(device_data[2] & 0b01111111, 100), 0)
|
|
16
16
|
_in_motion = bool(device_data[2] & 0b10000000)
|
|
17
|
-
_light_level = (device_data[
|
|
17
|
+
_light_level = (device_data[3] >> 4) & 0b00001111
|
|
18
18
|
_calibrated = bool(device_data[1] & 0b00000001)
|
|
19
19
|
|
|
20
20
|
return {
|
|
@@ -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
|
|
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((
|
|
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
|
|
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
|
+
}
|
|
@@ -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
|
|
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": (
|
|
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,6 +4,11 @@ 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
|
|
8
13
|
|
|
9
14
|
# Preserve old LockStatus export for backwards compatibility
|
|
@@ -78,6 +83,8 @@ class SwitchbotModel(StrEnum):
|
|
|
78
83
|
HUB3 = "Hub3"
|
|
79
84
|
LOCK_ULTRA = "Lock Ultra"
|
|
80
85
|
LOCK_LITE = "Lock Lite"
|
|
86
|
+
GARAGE_DOOR_OPENER = "Garage Door Opener"
|
|
87
|
+
RELAY_SWITCH_2PM = "Relay Switch 2PM"
|
|
81
88
|
|
|
82
89
|
|
|
83
90
|
__all__ = [
|
|
@@ -86,6 +93,9 @@ __all__ = [
|
|
|
86
93
|
"DEFAULT_SCAN_TIMEOUT",
|
|
87
94
|
"AirPurifierMode",
|
|
88
95
|
"FanMode",
|
|
96
|
+
"HumidifierAction",
|
|
97
|
+
"HumidifierMode",
|
|
98
|
+
"HumidifierWaterLevel",
|
|
89
99
|
"LockStatus",
|
|
90
100
|
"SwitchbotAccountConnectionError",
|
|
91
101
|
"SwitchbotApiError",
|
|
@@ -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,
|
|
@@ -116,7 +116,9 @@ def _merge_data(old_data: dict[str, Any], new_data: dict[str, Any]) -> dict[str,
|
|
|
116
116
|
"""Merge data but only add None keys if they are missing."""
|
|
117
117
|
merged = old_data.copy()
|
|
118
118
|
for key, value in new_data.items():
|
|
119
|
-
if value
|
|
119
|
+
if isinstance(value, dict) and isinstance(old_data.get(key), dict):
|
|
120
|
+
merged[key] = _merge_data(old_data[key], value)
|
|
121
|
+
elif value is not None or key not in old_data:
|
|
120
122
|
merged[key] = value
|
|
121
123
|
return merged
|
|
122
124
|
|
|
@@ -538,7 +540,7 @@ class SwitchbotBaseDevice:
|
|
|
538
540
|
self._override_adv_data.update(state)
|
|
539
541
|
self._update_parsed_data(state)
|
|
540
542
|
|
|
541
|
-
def _get_adv_value(self, key: str) -> Any:
|
|
543
|
+
def _get_adv_value(self, key: str, channel: int | None = None) -> Any:
|
|
542
544
|
"""Return value from advertisement data."""
|
|
543
545
|
if self._override_adv_data and key in self._override_adv_data:
|
|
544
546
|
_LOGGER.debug(
|
|
@@ -550,6 +552,8 @@ class SwitchbotBaseDevice:
|
|
|
550
552
|
return self._override_adv_data[key]
|
|
551
553
|
if not self._sb_adv_data:
|
|
552
554
|
return None
|
|
555
|
+
if channel is not None:
|
|
556
|
+
return self._sb_adv_data.data["data"].get(channel, {}).get(key)
|
|
553
557
|
return self._sb_adv_data.data["data"].get(key)
|
|
554
558
|
|
|
555
559
|
def get_battery_percent(self) -> Any:
|
|
@@ -583,11 +587,11 @@ class SwitchbotBaseDevice:
|
|
|
583
587
|
|
|
584
588
|
return self._sb_adv_data
|
|
585
589
|
|
|
586
|
-
async def _get_basic_info(
|
|
590
|
+
async def _get_basic_info(
|
|
591
|
+
self, cmd: str = DEVICE_GET_BASIC_SETTINGS_KEY
|
|
592
|
+
) -> bytes | None:
|
|
587
593
|
"""Return basic info of device."""
|
|
588
|
-
_data = await self._send_command(
|
|
589
|
-
key=DEVICE_GET_BASIC_SETTINGS_KEY, retry=self._retry_count
|
|
590
|
-
)
|
|
594
|
+
_data = await self._send_command(key=cmd, retry=self._retry_count)
|
|
591
595
|
|
|
592
596
|
if _data in (b"\x07", b"\x00"):
|
|
593
597
|
_LOGGER.error("Unsuccessful, please try again")
|