|
@@ -1,16 +1,29 @@
|
|
-from typing import Any
|
|
|
|
-from unittest.mock import Mock
|
|
|
|
|
|
+from unittest.mock import AsyncMock, Mock
|
|
|
|
|
|
import pytest
|
|
import pytest
|
|
from bleak.backends.device import BLEDevice
|
|
from bleak.backends.device import BLEDevice
|
|
|
|
|
|
from switchbot import SwitchBotAdvertisement, SwitchbotModel
|
|
from switchbot import SwitchBotAdvertisement, SwitchbotModel
|
|
from switchbot.devices import curtain
|
|
from switchbot.devices import curtain
|
|
|
|
+from switchbot.devices.base_cover import COVER_EXT_SUM_KEY
|
|
|
|
|
|
from .test_adv_parser import generate_ble_device
|
|
from .test_adv_parser import generate_ble_device
|
|
|
|
|
|
|
|
|
|
-def set_advertisement_data(ble_device: BLEDevice, in_motion: bool, position: int):
|
|
|
|
|
|
+def create_device_for_command_testing(calibration=True, reverse_mode=False):
|
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
|
+ curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
|
|
+ curtain_device.update_from_advertisement(
|
|
|
|
+ make_advertisement_data(ble_device, True, 50, calibration)
|
|
|
|
+ )
|
|
|
|
+ curtain_device._send_multiple_commands = AsyncMock()
|
|
|
|
+ curtain_device.update = AsyncMock()
|
|
|
|
+ return curtain_device
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def make_advertisement_data(
|
|
|
|
+ ble_device: BLEDevice, in_motion: bool, position: int, calibration: bool = True
|
|
|
|
+):
|
|
"""Set advertisement data with defaults."""
|
|
"""Set advertisement data with defaults."""
|
|
|
|
|
|
return SwitchBotAdvertisement(
|
|
return SwitchBotAdvertisement(
|
|
@@ -18,7 +31,7 @@ def set_advertisement_data(ble_device: BLEDevice, in_motion: bool, position: int
|
|
data={
|
|
data={
|
|
"rawAdvData": b"c\xc0X\x00\x11\x04",
|
|
"rawAdvData": b"c\xc0X\x00\x11\x04",
|
|
"data": {
|
|
"data": {
|
|
- "calibration": True,
|
|
|
|
|
|
+ "calibration": calibration,
|
|
"battery": 88,
|
|
"battery": 88,
|
|
"inMotion": in_motion,
|
|
"inMotion": in_motion,
|
|
"position": position,
|
|
"position": position,
|
|
@@ -42,7 +55,7 @@ def test_device_passive_not_in_motion(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, False, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, False, 0)
|
|
)
|
|
)
|
|
|
|
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_opening() is False
|
|
@@ -55,10 +68,10 @@ def test_device_passive_opening(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 10)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 10)
|
|
)
|
|
)
|
|
|
|
|
|
assert curtain_device.is_opening() is True
|
|
assert curtain_device.is_opening() is True
|
|
@@ -71,10 +84,10 @@ def test_device_passive_closing(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 100)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 100)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 90)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 90)
|
|
)
|
|
)
|
|
|
|
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_opening() is False
|
|
@@ -87,13 +100,13 @@ def test_device_passive_opening_then_stop(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 10)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 10)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, False, 10)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, False, 10)
|
|
)
|
|
)
|
|
|
|
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_opening() is False
|
|
@@ -106,13 +119,13 @@ def test_device_passive_closing_then_stop(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 100)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 100)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 90)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 90)
|
|
)
|
|
)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, False, 90)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, False, 90)
|
|
)
|
|
)
|
|
|
|
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_opening() is False
|
|
@@ -126,7 +139,7 @@ async def test_device_active_not_in_motion(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, False, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, False, 0)
|
|
)
|
|
)
|
|
|
|
|
|
basic_info = bytes([0, 0, 0, 0, 0, 0, 100, 0])
|
|
basic_info = bytes([0, 0, 0, 0, 0, 0, 100, 0])
|
|
@@ -149,7 +162,7 @@ async def test_device_active_opening(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
)
|
|
)
|
|
|
|
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
|
|
@@ -172,7 +185,7 @@ async def test_device_active_closing(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 100)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 100)
|
|
)
|
|
)
|
|
|
|
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
|
|
@@ -195,7 +208,7 @@ async def test_device_active_opening_then_stop(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 0)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
)
|
|
)
|
|
|
|
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
|
|
@@ -222,7 +235,7 @@ async def test_device_active_closing_then_stop(reverse_mode):
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device = curtain.SwitchbotCurtain(ble_device, reverse_mode=reverse_mode)
|
|
curtain_device.update_from_advertisement(
|
|
curtain_device.update_from_advertisement(
|
|
- set_advertisement_data(ble_device, True, 100)
|
|
|
|
|
|
+ make_advertisement_data(ble_device, True, 100)
|
|
)
|
|
)
|
|
|
|
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
|
|
basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
|
|
@@ -240,3 +253,174 @@ async def test_device_active_closing_then_stop(reverse_mode):
|
|
|
|
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_opening() is False
|
|
assert curtain_device.is_closing() is False
|
|
assert curtain_device.is_closing() is False
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_get_basic_info_returns_none_when_no_data():
|
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
|
+ curtain_device = curtain.SwitchbotCurtain(ble_device)
|
|
|
|
+ curtain_device.update_from_advertisement(
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
|
|
+ )
|
|
|
|
+ curtain_device._get_basic_info = AsyncMock(return_value=None)
|
|
|
|
+
|
|
|
|
+ assert await curtain_device.get_basic_info() is None
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+@pytest.mark.parametrize(
|
|
|
|
+ "data,result",
|
|
|
|
+ [
|
|
|
|
+ (
|
|
|
|
+ bytes([0, 1, 10, 2, 255, 255, 50, 4]),
|
|
|
|
+ [1, 1, 2, "right_to_left", 1, 1, 50, 4],
|
|
|
|
+ ),
|
|
|
|
+ (bytes([0, 1, 10, 2, 0, 0, 50, 4]), [1, 1, 2, "left_to_right", 0, 0, 50, 4]),
|
|
|
|
+ ],
|
|
|
|
+)
|
|
|
|
+async def test_get_basic_info(data, result):
|
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
|
+ curtain_device = curtain.SwitchbotCurtain(ble_device)
|
|
|
|
+ curtain_device.update_from_advertisement(
|
|
|
|
+ make_advertisement_data(ble_device, True, 0)
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ async def custom_implementation():
|
|
|
|
+ return data
|
|
|
|
+
|
|
|
|
+ curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
|
|
|
|
+
|
|
|
|
+ info = await curtain_device.get_basic_info()
|
|
|
|
+ assert info["battery"] == result[0]
|
|
|
|
+ assert info["firmware"] == result[1]
|
|
|
|
+ assert info["chainLength"] == result[2]
|
|
|
|
+ assert info["openDirection"] == result[3]
|
|
|
|
+ assert info["touchToOpen"] == result[4]
|
|
|
|
+ assert info["light"] == result[4]
|
|
|
|
+ assert info["fault"] == result[4]
|
|
|
|
+ assert info["solarPanel"] == result[5]
|
|
|
|
+ assert info["calibration"] == result[5]
|
|
|
|
+ assert info["calibrated"] == result[5]
|
|
|
|
+ assert info["inMotion"] == result[5]
|
|
|
|
+ assert info["position"] == result[6]
|
|
|
|
+ assert info["timers"] == result[7]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_open():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ await curtain_device.open()
|
|
|
|
+ assert curtain_device.is_opening() is True
|
|
|
|
+ assert curtain_device.is_closing() is False
|
|
|
|
+ curtain_device._send_multiple_commands.assert_awaited_once()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_close():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ await curtain_device.close()
|
|
|
|
+ assert curtain_device.is_opening() is False
|
|
|
|
+ assert curtain_device.is_closing() is True
|
|
|
|
+ curtain_device._send_multiple_commands.assert_awaited_once()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_stop():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ await curtain_device.stop()
|
|
|
|
+ assert curtain_device.is_opening() is False
|
|
|
|
+ assert curtain_device.is_closing() is False
|
|
|
|
+ curtain_device._send_multiple_commands.assert_awaited_once()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_set_position_opening():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ await curtain_device.set_position(100)
|
|
|
|
+ assert curtain_device.is_opening() is True
|
|
|
|
+ assert curtain_device.is_closing() is False
|
|
|
|
+ curtain_device._send_multiple_commands.assert_awaited_once()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_set_position_closing():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ await curtain_device.set_position(0)
|
|
|
|
+ assert curtain_device.is_opening() is False
|
|
|
|
+ assert curtain_device.is_closing() is True
|
|
|
|
+ curtain_device._send_multiple_commands.assert_awaited_once()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def test_get_position():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ assert curtain_device.get_position() == 50
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+async def test_get_extended_info_summary_sends_command():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ curtain_device._send_command = AsyncMock()
|
|
|
|
+ await curtain_device.get_extended_info_summary()
|
|
|
|
+ curtain_device._send_command.assert_awaited_once_with(key=COVER_EXT_SUM_KEY)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+@pytest.mark.parametrize("data_value", [(None), (b"\x07"), (b"\x00")])
|
|
|
|
+async def test_get_extended_info_summary_returns_none_when_bad_data(data_value):
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ curtain_device._send_command = AsyncMock(return_value=data_value)
|
|
|
|
+ assert await curtain_device.get_extended_info_summary() is None
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+@pytest.mark.parametrize(
|
|
|
|
+ "data,result",
|
|
|
|
+ [
|
|
|
|
+ ([0, 0, 0], [True, False, False, "right_to_left"]),
|
|
|
|
+ ([255, 255, 0], [False, True, True, "left_to_right"]),
|
|
|
|
+ ],
|
|
|
|
+)
|
|
|
|
+async def test_get_extended_info_summary_returns_device0(data, result):
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ curtain_device._send_command = AsyncMock(return_value=bytes(data))
|
|
|
|
+ ext_result = await curtain_device.get_extended_info_summary()
|
|
|
|
+ assert ext_result["device0"]["openDirectionDefault"] == result[0]
|
|
|
|
+ assert ext_result["device0"]["touchToOpen"] == result[1]
|
|
|
|
+ assert ext_result["device0"]["light"] == result[2]
|
|
|
|
+ assert ext_result["device0"]["openDirection"] == result[3]
|
|
|
|
+ assert "device1" not in ext_result
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
+@pytest.mark.parametrize(
|
|
|
|
+ "data,result",
|
|
|
|
+ [
|
|
|
|
+ ([0, 0, 1], [True, False, False, "right_to_left"]),
|
|
|
|
+ ([255, 255, 255], [False, True, True, "left_to_right"]),
|
|
|
|
+ ],
|
|
|
|
+)
|
|
|
|
+async def test_get_extended_info_summary_returns_device1(data, result):
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ curtain_device._send_command = AsyncMock(return_value=bytes(data))
|
|
|
|
+ ext_result = await curtain_device.get_extended_info_summary()
|
|
|
|
+ assert ext_result["device1"]["openDirectionDefault"] == result[0]
|
|
|
|
+ assert ext_result["device1"]["touchToOpen"] == result[1]
|
|
|
|
+ assert ext_result["device1"]["light"] == result[2]
|
|
|
|
+ assert ext_result["device1"]["openDirection"] == result[3]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def test_get_light_level():
|
|
|
|
+ curtain_device = create_device_for_command_testing()
|
|
|
|
+ assert curtain_device.get_light_level() == 1
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.parametrize("reverse_mode", [(True), (False)])
|
|
|
|
+def test_is_reversed(reverse_mode):
|
|
|
|
+ curtain_device = create_device_for_command_testing(reverse_mode=reverse_mode)
|
|
|
|
+ assert curtain_device.is_reversed() == reverse_mode
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@pytest.mark.parametrize("calibration", [(True), (False)])
|
|
|
|
+def test_is_calibrated(calibration):
|
|
|
|
+ curtain_device = create_device_for_command_testing(calibration=calibration)
|
|
|
|
+ assert curtain_device.is_calibrated() == calibration
|