PySwitchbot 2.0.0__tar.gz → 2.1.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-2.0.0 → pyswitchbot-2.1.0}/PKG-INFO +11 -3
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/PySwitchbot.egg-info/PKG-INFO +11 -3
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/README.md +10 -2
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/setup.py +1 -1
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/keypad_vision.py +2 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/lock.py +30 -2
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_adv_parser.py +4 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_lock.py +72 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/LICENSE +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/MANIFEST.in +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/PySwitchbot.egg-info/SOURCES.txt +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/pyproject.toml +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/setup.cfg +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/__init__.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parser.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/air_purifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/art_frame.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/climate_panel.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/fan.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/hub2.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/hub3.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/hubmini_matter.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/humidifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/keypad.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/leak.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/lock.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/meter.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/plug.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/presence_sensor.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/relay_switch.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/remote.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/roller_shade.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/smart_thermostat_radiator.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/adv_parsers/vacuum.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/__init__.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/air_purifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/climate.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/evaporative_humidifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/fan.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/hub2.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/hub3.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/lock.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/const/presence_sensor.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/air_purifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/art_frame.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/base_cover.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/device.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/evaporative_humidifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/fan.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/keypad_vision.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/meter_pro.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/relay_switch.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/roller_shade.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/smart_thermostat_radiator.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/devices/vacuum.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/discovery.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/enum.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/helpers.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/models.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/switchbot/utils.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_air_purifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_art_frame.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_base_cover.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_blind_tilt.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_bulb.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_ceiling_light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_colormode_imports.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_curtain.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_device.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_discovery_callback.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_encrypted_device.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_evaporative_humidifier.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_fan.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_helpers.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_hub2.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_hub3.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_keypad_vision.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_meter_pro.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_relay_switch.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_roller_shade.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_smart_thermostat_radiator.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_strip_light.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_utils.py +0 -0
- {pyswitchbot-2.0.0 → pyswitchbot-2.1.0}/tests/test_vacuum.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySwitchbot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -33,7 +33,15 @@ Dynamic: summary
|
|
|
33
33
|
|
|
34
34
|
# pySwitchbot [](https://codecov.io/gh/sblibs/pySwitchbot)
|
|
35
35
|
|
|
36
|
-
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
36
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
37
|
+
|
|
38
|
+
## Setting up the environment
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
python3 -m venv .venv
|
|
42
|
+
source .venv/bin/activate
|
|
43
|
+
pip install .
|
|
44
|
+
```
|
|
37
45
|
|
|
38
46
|
## Obtaining encryption key for Switchbot Locks
|
|
39
47
|
|
|
@@ -42,7 +50,7 @@ Using the script `scripts/get_encryption_key.py` you can manually obtain locks e
|
|
|
42
50
|
Usage:
|
|
43
51
|
|
|
44
52
|
```shell
|
|
45
|
-
$ python3 get_encryption_key.py MAC USERNAME
|
|
53
|
+
$ python3 scripts/get_encryption_key.py MAC USERNAME
|
|
46
54
|
Key ID: XX
|
|
47
55
|
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
48
56
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySwitchbot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -33,7 +33,15 @@ Dynamic: summary
|
|
|
33
33
|
|
|
34
34
|
# pySwitchbot [](https://codecov.io/gh/sblibs/pySwitchbot)
|
|
35
35
|
|
|
36
|
-
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
36
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
37
|
+
|
|
38
|
+
## Setting up the environment
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
python3 -m venv .venv
|
|
42
|
+
source .venv/bin/activate
|
|
43
|
+
pip install .
|
|
44
|
+
```
|
|
37
45
|
|
|
38
46
|
## Obtaining encryption key for Switchbot Locks
|
|
39
47
|
|
|
@@ -42,7 +50,7 @@ Using the script `scripts/get_encryption_key.py` you can manually obtain locks e
|
|
|
42
50
|
Usage:
|
|
43
51
|
|
|
44
52
|
```shell
|
|
45
|
-
$ python3 get_encryption_key.py MAC USERNAME
|
|
53
|
+
$ python3 scripts/get_encryption_key.py MAC USERNAME
|
|
46
54
|
Key ID: XX
|
|
47
55
|
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
48
56
|
```
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# pySwitchbot [](https://codecov.io/gh/sblibs/pySwitchbot)
|
|
2
2
|
|
|
3
|
-
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
3
|
+
Library to control Switchbot IoT devices https://www.switch-bot.com/
|
|
4
|
+
|
|
5
|
+
## Setting up the environment
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
python3 -m venv .venv
|
|
9
|
+
source .venv/bin/activate
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
4
12
|
|
|
5
13
|
## Obtaining encryption key for Switchbot Locks
|
|
6
14
|
|
|
@@ -9,7 +17,7 @@ Using the script `scripts/get_encryption_key.py` you can manually obtain locks e
|
|
|
9
17
|
Usage:
|
|
10
18
|
|
|
11
19
|
```shell
|
|
12
|
-
$ python3 get_encryption_key.py MAC USERNAME
|
|
20
|
+
$ python3 scripts/get_encryption_key.py MAC USERNAME
|
|
13
21
|
Key ID: XX
|
|
14
22
|
Encryption key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
15
23
|
```
|
|
@@ -19,6 +19,7 @@ def process_common_mfr_data(mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
|
19
19
|
low_temperature = bool(mfr_data[8] & 0b10000000)
|
|
20
20
|
high_temperature = bool(mfr_data[8] & 0b01000000)
|
|
21
21
|
doorbell = bool(mfr_data[12] & 0b00001000)
|
|
22
|
+
doorbell_seq = mfr_data[12] & 0b00000111
|
|
22
23
|
|
|
23
24
|
return {
|
|
24
25
|
"sequence_number": sequence_number,
|
|
@@ -30,6 +31,7 @@ def process_common_mfr_data(mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
|
30
31
|
"low_temperature": low_temperature,
|
|
31
32
|
"high_temperature": high_temperature,
|
|
32
33
|
"doorbell": doorbell,
|
|
34
|
+
"doorbell_seq": doorbell_seq,
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
|
|
@@ -10,7 +10,11 @@ from bleak.backends.device import BLEDevice
|
|
|
10
10
|
|
|
11
11
|
from ..const import SwitchbotModel
|
|
12
12
|
from ..const.lock import LockStatus
|
|
13
|
-
from .device import
|
|
13
|
+
from .device import (
|
|
14
|
+
SwitchbotEncryptedDevice,
|
|
15
|
+
SwitchbotOperationError,
|
|
16
|
+
SwitchbotSequenceDevice,
|
|
17
|
+
)
|
|
14
18
|
|
|
15
19
|
COMMAND_HEADER = "57"
|
|
16
20
|
COMMAND_LOCK_INFO = {
|
|
@@ -49,6 +53,10 @@ COMMAND_LOCK = {
|
|
|
49
53
|
SwitchbotModel.LOCK_VISION: f"{COMMAND_HEADER}0f4e0101000000",
|
|
50
54
|
SwitchbotModel.LOCK_PRO_WIFI: f"{COMMAND_HEADER}0f4e0101000000",
|
|
51
55
|
}
|
|
56
|
+
COMMAND_HALF_LOCK = {
|
|
57
|
+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e0101000008",
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
COMMAND_ENABLE_NOTIFICATIONS = {
|
|
53
61
|
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0e01001e00008101",
|
|
54
62
|
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0e01001e00008101",
|
|
@@ -129,6 +137,19 @@ class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
|
|
|
129
137
|
{LockStatus.UNLOCKED, LockStatus.UNLOCKING, LockStatus.NOT_FULLY_LOCKED},
|
|
130
138
|
)
|
|
131
139
|
|
|
140
|
+
async def half_lock(self) -> bool:
|
|
141
|
+
"""Send half lock command (Lock Ultra EU type only)."""
|
|
142
|
+
if self._model not in COMMAND_HALF_LOCK:
|
|
143
|
+
raise SwitchbotOperationError(
|
|
144
|
+
f"Half lock is not supported on {self._model}"
|
|
145
|
+
)
|
|
146
|
+
if not self.is_half_lock_calibrated():
|
|
147
|
+
raise SwitchbotOperationError("Half lock is not calibrated")
|
|
148
|
+
return await self._lock_unlock(
|
|
149
|
+
COMMAND_HALF_LOCK[self._model],
|
|
150
|
+
{LockStatus.HALF_LOCKED, LockStatus.LOCKING},
|
|
151
|
+
)
|
|
152
|
+
|
|
132
153
|
def _parse_basic_data(self, basic_data: bytes) -> dict[str, Any]:
|
|
133
154
|
"""Parse basic data from lock."""
|
|
134
155
|
return {
|
|
@@ -207,6 +228,10 @@ class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
|
|
|
207
228
|
"""Return True if Night Latch is enabled on EU firmware."""
|
|
208
229
|
return self._get_adv_value("night_latch")
|
|
209
230
|
|
|
231
|
+
def is_half_lock_calibrated(self) -> bool | None:
|
|
232
|
+
"""Return True if half lock position is calibrated (Lock Ultra only)."""
|
|
233
|
+
return self._get_adv_value("half_lock_calibration")
|
|
234
|
+
|
|
210
235
|
async def _get_lock_info(self) -> bytes | None:
|
|
211
236
|
"""Return lock info of device."""
|
|
212
237
|
_data = await self._send_command(
|
|
@@ -268,10 +293,13 @@ class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
|
|
|
268
293
|
"status": LockStatus((data[0] & 0b01110000) >> 4),
|
|
269
294
|
"unlocked_alarm": bool(data[1] & 0b00010000),
|
|
270
295
|
}
|
|
271
|
-
|
|
296
|
+
result = {
|
|
272
297
|
"calibration": bool(data[0] & 0b10000000),
|
|
273
298
|
"status": LockStatus((data[0] & 0b01111000) >> 3),
|
|
274
299
|
"door_open": bool(data[1] & 0b00010000),
|
|
275
300
|
"unclosed_alarm": bool(data[5] & 0b10000000),
|
|
276
301
|
"unlocked_alarm": bool(data[5] & 0b01000000),
|
|
277
302
|
}
|
|
303
|
+
if model is SwitchbotModel.LOCK_ULTRA:
|
|
304
|
+
result["half_lock_calibration"] = bool(data[1] & 0b00000001)
|
|
305
|
+
return result
|
|
@@ -3718,6 +3718,7 @@ def test_humidifer_with_empty_data() -> None:
|
|
|
3718
3718
|
"battery": 95,
|
|
3719
3719
|
"battery_charging": True,
|
|
3720
3720
|
"doorbell": False,
|
|
3721
|
+
"doorbell_seq": 0,
|
|
3721
3722
|
"duress_alarm": False,
|
|
3722
3723
|
"high_temperature": False,
|
|
3723
3724
|
"lockout_alarm": False,
|
|
@@ -3737,6 +3738,7 @@ def test_humidifer_with_empty_data() -> None:
|
|
|
3737
3738
|
"battery": 96,
|
|
3738
3739
|
"battery_charging": False,
|
|
3739
3740
|
"doorbell": False,
|
|
3741
|
+
"doorbell_seq": 0,
|
|
3740
3742
|
"duress_alarm": False,
|
|
3741
3743
|
"high_temperature": False,
|
|
3742
3744
|
"lockout_alarm": False,
|
|
@@ -4057,6 +4059,7 @@ def test_adv_active(test_case: AdvTestCase) -> None:
|
|
|
4057
4059
|
"battery": 95,
|
|
4058
4060
|
"battery_charging": True,
|
|
4059
4061
|
"doorbell": False,
|
|
4062
|
+
"doorbell_seq": 0,
|
|
4060
4063
|
"duress_alarm": False,
|
|
4061
4064
|
"high_temperature": False,
|
|
4062
4065
|
"lockout_alarm": False,
|
|
@@ -4076,6 +4079,7 @@ def test_adv_active(test_case: AdvTestCase) -> None:
|
|
|
4076
4079
|
"battery": 96,
|
|
4077
4080
|
"battery_charging": False,
|
|
4078
4081
|
"doorbell": False,
|
|
4082
|
+
"doorbell_seq": 0,
|
|
4079
4083
|
"duress_alarm": False,
|
|
4080
4084
|
"high_temperature": False,
|
|
4081
4085
|
"lockout_alarm": False,
|
|
@@ -6,6 +6,7 @@ import pytest
|
|
|
6
6
|
from switchbot import SwitchbotModel
|
|
7
7
|
from switchbot.const.lock import LockStatus
|
|
8
8
|
from switchbot.devices import lock
|
|
9
|
+
from switchbot.devices.device import SwitchbotOperationError
|
|
9
10
|
|
|
10
11
|
from .test_adv_parser import generate_ble_device
|
|
11
12
|
|
|
@@ -624,6 +625,7 @@ def test_update_lock_status(model: str):
|
|
|
624
625
|
"door_open": True,
|
|
625
626
|
"unclosed_alarm": True,
|
|
626
627
|
"unlocked_alarm": True,
|
|
628
|
+
"half_lock_calibration": False,
|
|
627
629
|
},
|
|
628
630
|
),
|
|
629
631
|
(
|
|
@@ -712,6 +714,7 @@ def test_parse_lock_data(model: str, data: bytes, expected: dict):
|
|
|
712
714
|
"door_open": False,
|
|
713
715
|
"unclosed_alarm": False,
|
|
714
716
|
"unlocked_alarm": True, # bit 6 of byte 5
|
|
717
|
+
"half_lock_calibration": False,
|
|
715
718
|
},
|
|
716
719
|
),
|
|
717
720
|
(
|
|
@@ -760,6 +763,75 @@ async def test_lock_with_update(model: str):
|
|
|
760
763
|
assert result is True
|
|
761
764
|
|
|
762
765
|
|
|
766
|
+
def test_is_half_lock_calibrated():
|
|
767
|
+
"""Test is_half_lock_calibrated method."""
|
|
768
|
+
device = create_device_for_command_testing(SwitchbotModel.LOCK_ULTRA)
|
|
769
|
+
device._get_adv_value = Mock(return_value=True)
|
|
770
|
+
assert device.is_half_lock_calibrated() is True
|
|
771
|
+
|
|
772
|
+
device._get_adv_value = Mock(return_value=False)
|
|
773
|
+
assert device.is_half_lock_calibrated() is False
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
@pytest.mark.asyncio
|
|
777
|
+
async def test_half_lock_calibrated():
|
|
778
|
+
"""Test half_lock succeeds when calibrated."""
|
|
779
|
+
device = create_device_for_command_testing(SwitchbotModel.LOCK_ULTRA)
|
|
780
|
+
device._get_adv_value = Mock(side_effect=[True, LockStatus.LOCKED])
|
|
781
|
+
with (
|
|
782
|
+
patch.object(device, "_send_command", return_value=b"\x01\x00"),
|
|
783
|
+
patch.object(device, "_enable_notifications", return_value=True),
|
|
784
|
+
patch.object(device, "_get_basic_info", return_value=b"\x01\x64\x01"),
|
|
785
|
+
):
|
|
786
|
+
result = await device.half_lock()
|
|
787
|
+
assert result is True
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
@pytest.mark.asyncio
|
|
791
|
+
async def test_half_lock_not_calibrated():
|
|
792
|
+
"""Test half_lock raises SwitchbotOperationError when not calibrated."""
|
|
793
|
+
device = create_device_for_command_testing(SwitchbotModel.LOCK_ULTRA)
|
|
794
|
+
device._get_adv_value = Mock(return_value=False)
|
|
795
|
+
with pytest.raises(SwitchbotOperationError, match="not calibrated"):
|
|
796
|
+
await device.half_lock()
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
@pytest.mark.asyncio
|
|
800
|
+
@pytest.mark.parametrize(
|
|
801
|
+
"model",
|
|
802
|
+
[
|
|
803
|
+
SwitchbotModel.LOCK,
|
|
804
|
+
SwitchbotModel.LOCK_LITE,
|
|
805
|
+
SwitchbotModel.LOCK_PRO,
|
|
806
|
+
SwitchbotModel.LOCK_VISION,
|
|
807
|
+
SwitchbotModel.LOCK_VISION_PRO,
|
|
808
|
+
SwitchbotModel.LOCK_PRO_WIFI,
|
|
809
|
+
],
|
|
810
|
+
)
|
|
811
|
+
async def test_half_lock_unsupported_model(model: str):
|
|
812
|
+
"""Test half_lock raises SwitchbotOperationError on unsupported models."""
|
|
813
|
+
device = create_device_for_command_testing(model)
|
|
814
|
+
with pytest.raises(SwitchbotOperationError, match="not supported"):
|
|
815
|
+
await device.half_lock()
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@pytest.mark.asyncio
|
|
819
|
+
async def test_half_lock():
|
|
820
|
+
"""Test half_lock method."""
|
|
821
|
+
device = create_device_for_command_testing(SwitchbotModel.LOCK_ULTRA)
|
|
822
|
+
device._get_adv_value = Mock(side_effect=[True, LockStatus.LOCKED])
|
|
823
|
+
with (
|
|
824
|
+
patch.object(device, "_send_command", return_value=b"\x01\x00") as mock_send,
|
|
825
|
+
patch.object(device, "_enable_notifications", return_value=True),
|
|
826
|
+
patch.object(device, "_get_basic_info", return_value=b"\x01\x64\x01"),
|
|
827
|
+
):
|
|
828
|
+
result = await device.half_lock()
|
|
829
|
+
assert result is True
|
|
830
|
+
mock_send.assert_awaited_once_with(
|
|
831
|
+
lock.COMMAND_HALF_LOCK[SwitchbotModel.LOCK_ULTRA]
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
763
835
|
@pytest.mark.asyncio
|
|
764
836
|
@pytest.mark.parametrize(
|
|
765
837
|
("model", "status"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|