|
|
@@ -24,6 +24,9 @@ def create_device_for_command_testing(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_lock_init(model: str):
|
|
|
@@ -54,6 +57,9 @@ def test_lock_init_with_invalid_model(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_verify_encryption_key(model: str):
|
|
|
@@ -75,6 +81,9 @@ async def test_verify_encryption_key(model: str):
|
|
|
(SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
|
|
|
(SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x85"),
|
|
|
(SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x86"),
|
|
|
+ (SwitchbotModel.LOCK_VISION, b"W\x0fN\x01\x01\x00\x80"),
|
|
|
+ (SwitchbotModel.LOCK_VISION_PRO, b"W\x0fN\x01\x01\x00\x80"),
|
|
|
+ (SwitchbotModel.LOCK_PRO_WIFI, b"W\x0fN\x01\x01\x10\x82"),
|
|
|
],
|
|
|
)
|
|
|
async def test_lock(model: str, command: bytes):
|
|
|
@@ -82,12 +91,15 @@ async def test_lock(model: str, command: bytes):
|
|
|
device = create_device_for_command_testing(model)
|
|
|
device._get_adv_value = Mock(return_value=LockStatus.UNLOCKED)
|
|
|
with (
|
|
|
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
|
|
|
+ patch.object(
|
|
|
+ device, "_send_command", return_value=b"\x01\x00"
|
|
|
+ ) as mock_send_command,
|
|
|
patch.object(device, "_enable_notifications", return_value=True),
|
|
|
patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
|
|
|
):
|
|
|
result = await device.lock()
|
|
|
assert result is True
|
|
|
+ mock_send_command.assert_any_call(lock.COMMAND_LOCK[model])
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
@@ -98,6 +110,9 @@ async def test_lock(model: str, command: bytes):
|
|
|
(SwitchbotModel.LOCK_LITE, b"W\x0fN\x01\x01\x10\x81"),
|
|
|
(SwitchbotModel.LOCK_PRO, b"W\x0fN\x01\x01\x10\x84"),
|
|
|
(SwitchbotModel.LOCK_ULTRA, b"W\x0fN\x01\x01\x10\x83"),
|
|
|
+ (SwitchbotModel.LOCK_VISION, b"W\x0fN\x01\x01\x00\x80"),
|
|
|
+ (SwitchbotModel.LOCK_VISION_PRO, b"W\x0fN\x01\x01\x00\x80"),
|
|
|
+ (SwitchbotModel.LOCK_PRO_WIFI, b"W\x0fN\x01\x01\x10\x81"),
|
|
|
],
|
|
|
)
|
|
|
async def test_unlock(model: str, command: bytes):
|
|
|
@@ -105,12 +120,15 @@ async def test_unlock(model: str, command: bytes):
|
|
|
device = create_device_for_command_testing(model)
|
|
|
device._get_adv_value = Mock(return_value=LockStatus.LOCKED)
|
|
|
with (
|
|
|
- patch.object(device, "_send_command", return_value=b"\x01\x00"),
|
|
|
+ patch.object(
|
|
|
+ device, "_send_command", return_value=b"\x01\x00"
|
|
|
+ ) as mock_send_command,
|
|
|
patch.object(device, "_enable_notifications", return_value=True),
|
|
|
patch.object(device, "_get_basic_info", return_value=b"\x00\x64\x01"),
|
|
|
):
|
|
|
result = await device.unlock()
|
|
|
assert result is True
|
|
|
+ mock_send_command.assert_any_call(lock.COMMAND_UNLOCK[model])
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
@@ -121,6 +139,9 @@ async def test_unlock(model: str, command: bytes):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_unlock_without_unlatch(model: str):
|
|
|
@@ -144,6 +165,9 @@ async def test_unlock_without_unlatch(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_get_basic_info(model: str):
|
|
|
@@ -171,6 +195,9 @@ async def test_get_basic_info(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_get_basic_info_no_lock_data(model: str):
|
|
|
@@ -189,6 +216,9 @@ async def test_get_basic_info_no_lock_data(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_get_basic_info_no_basic_data(model: str):
|
|
|
@@ -219,6 +249,9 @@ def test_parse_basic_data():
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_is_calibrated(model: str):
|
|
|
@@ -235,6 +268,9 @@ def test_is_calibrated(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_get_lock_status(model: str):
|
|
|
@@ -250,6 +286,8 @@ def test_get_lock_status(model: str):
|
|
|
SwitchbotModel.LOCK,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_is_door_open(model: str):
|
|
|
@@ -265,6 +303,8 @@ def test_is_door_open(model: str):
|
|
|
SwitchbotModel.LOCK,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_is_unclosed_alarm_on(model: str):
|
|
|
@@ -281,6 +321,9 @@ def test_is_unclosed_alarm_on(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_is_unlocked_alarm_on(model: str):
|
|
|
@@ -310,6 +353,9 @@ def test_is_auto_lock_paused(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_is_night_latch_enabled(model: str):
|
|
|
@@ -327,6 +373,9 @@ def test_is_night_latch_enabled(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_get_lock_info(model: str):
|
|
|
@@ -346,6 +395,9 @@ async def test_get_lock_info(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_get_lock_info_failure(model: str):
|
|
|
@@ -364,6 +416,9 @@ async def test_get_lock_info_failure(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_enable_notifications(model: str):
|
|
|
@@ -382,6 +437,9 @@ async def test_enable_notifications(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_disable_notifications(model: str):
|
|
|
@@ -402,6 +460,9 @@ async def test_disable_notifications(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_disable_notifications_already_disabled(model: str):
|
|
|
@@ -421,6 +482,9 @@ async def test_disable_notifications_already_disabled(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_notification_handler(model: str):
|
|
|
@@ -440,6 +504,9 @@ def test_notification_handler(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_notification_handler_not_enabled(model: str):
|
|
|
@@ -465,6 +532,9 @@ def test_notification_handler_not_enabled(model: str):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_notification_handler_during_disconnect(
|
|
|
@@ -493,6 +563,9 @@ def test_notification_handler_during_disconnect(
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
def test_update_lock_status(model: str):
|
|
|
@@ -553,6 +626,37 @@ def test_update_lock_status(model: str):
|
|
|
"unlocked_alarm": True,
|
|
|
},
|
|
|
),
|
|
|
+ (
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ b"\x80\x00\x00\x00\x00\x00",
|
|
|
+ {
|
|
|
+ "calibration": True,
|
|
|
+ "status": LockStatus.LOCKED, # (0x80 & 0b01110000) >> 4 = 0 = LOCKED
|
|
|
+ "unlocked_alarm": False,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ b"\x80\x00\x00\x00\x00\x00",
|
|
|
+ {
|
|
|
+ "calibration": True,
|
|
|
+ "status": LockStatus.LOCKED, # (0x80 & 0b01111000) >> 3 = 0 = LOCKED
|
|
|
+ "door_open": False,
|
|
|
+ "unclosed_alarm": False,
|
|
|
+ "unlocked_alarm": False,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
+ b"\x80\x00\x00\x00\x00\x00",
|
|
|
+ {
|
|
|
+ "calibration": True,
|
|
|
+ "status": LockStatus.LOCKED, # (0x80 & 0b01111000) >> 3 = 0 = LOCKED
|
|
|
+ "door_open": False,
|
|
|
+ "unclosed_alarm": False,
|
|
|
+ "unlocked_alarm": False,
|
|
|
+ },
|
|
|
+ ),
|
|
|
],
|
|
|
)
|
|
|
def test_parse_lock_data(model: str, data: bytes, expected: dict):
|
|
|
@@ -610,6 +714,17 @@ def test_parse_lock_data(model: str, data: bytes, expected: dict):
|
|
|
"unlocked_alarm": True, # bit 6 of byte 5
|
|
|
},
|
|
|
),
|
|
|
+ (
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
+ b"\x90\x10\x00\x00\x00\xc0", # New format: status bits 3-6, door open bit 4 of byte 1
|
|
|
+ {
|
|
|
+ "calibration": True,
|
|
|
+ "status": LockStatus.LOCKING, # (0x90 & 0b01111000) >> 3 = 0x10 >> 3 = 2 (LOCKING)
|
|
|
+ "door_open": True, # bit 4 of byte 1 (0x10)
|
|
|
+ "unclosed_alarm": True, # bit 7 of byte 5 (0xc0)
|
|
|
+ "unlocked_alarm": True, # bit 6 of byte 5 (0xc0)
|
|
|
+ },
|
|
|
+ ),
|
|
|
],
|
|
|
)
|
|
|
def test_parse_lock_data_new_formats(model: str, data: bytes, expected: dict):
|
|
|
@@ -626,6 +741,9 @@ def test_parse_lock_data_new_formats(model: str, data: bytes, expected: dict):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_lock_with_update(model: str):
|
|
|
@@ -650,6 +768,9 @@ async def test_lock_with_update(model: str):
|
|
|
(SwitchbotModel.LOCK_LITE, LockStatus.LOCKING),
|
|
|
(SwitchbotModel.LOCK_PRO, LockStatus.LOCKED),
|
|
|
(SwitchbotModel.LOCK_ULTRA, LockStatus.LOCKING),
|
|
|
+ (SwitchbotModel.LOCK_VISION, LockStatus.LOCKED),
|
|
|
+ (SwitchbotModel.LOCK_VISION_PRO, LockStatus.LOCKED),
|
|
|
+ (SwitchbotModel.LOCK_PRO_WIFI, LockStatus.LOCKED),
|
|
|
],
|
|
|
)
|
|
|
async def test_lock_already_locked(model: str, status: LockStatus):
|
|
|
@@ -670,6 +791,9 @@ async def test_lock_already_locked(model: str, status: LockStatus):
|
|
|
SwitchbotModel.LOCK_LITE,
|
|
|
SwitchbotModel.LOCK_PRO,
|
|
|
SwitchbotModel.LOCK_ULTRA,
|
|
|
+ SwitchbotModel.LOCK_VISION,
|
|
|
+ SwitchbotModel.LOCK_VISION_PRO,
|
|
|
+ SwitchbotModel.LOCK_PRO_WIFI,
|
|
|
],
|
|
|
)
|
|
|
async def test_lock_with_invalid_basic_data(model: str):
|