123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- from unittest.mock import AsyncMock, Mock
- import pytest
- from bleak.backends.device import BLEDevice
- from switchbot import SwitchBotAdvertisement, SwitchbotModel
- from switchbot.devices import curtain
- from switchbot.devices.base_cover import COVER_EXT_SUM_KEY
- from .test_adv_parser import generate_ble_device
- 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."""
- return SwitchBotAdvertisement(
- address="aa:bb:cc:dd:ee:ff",
- data={
- "rawAdvData": b"c\xc0X\x00\x11\x04",
- "data": {
- "calibration": calibration,
- "battery": 88,
- "inMotion": in_motion,
- "position": position,
- "lightLevel": 1,
- "deviceChain": 1,
- },
- "isEncrypted": False,
- "model": "c",
- "modelFriendlyName": "Curtain",
- "modelName": SwitchbotModel.CURTAIN,
- },
- device=ble_device,
- rssi=-80,
- active=True,
- )
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- def test_device_passive_not_in_motion(reverse_mode):
- """Test passive not in motion advertisement."""
- 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, False, 0)
- )
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is False
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- def test_device_passive_opening(reverse_mode):
- """Test passive opening advertisement."""
- 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, 0)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, True, 10)
- )
- assert curtain_device.is_opening() is True
- assert curtain_device.is_closing() is False
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- def test_device_passive_closing(reverse_mode):
- """Test passive closing advertisement."""
- 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, 100)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, True, 90)
- )
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is True
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- def test_device_passive_opening_then_stop(reverse_mode):
- """Test passive stopped after opening advertisement."""
- 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, 0)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, True, 10)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, False, 10)
- )
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is False
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- def test_device_passive_closing_then_stop(reverse_mode):
- """Test passive stopped after closing advertisement."""
- 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, 100)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, True, 90)
- )
- curtain_device.update_from_advertisement(
- make_advertisement_data(ble_device, False, 90)
- )
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is False
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- async def test_device_active_not_in_motion(reverse_mode):
- """Test active not in motion."""
- 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, False, 0)
- )
- basic_info = bytes([0, 0, 0, 0, 0, 0, 100, 0])
- async def custom_implementation():
- return basic_info
- curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
- await curtain_device.get_basic_info()
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is False
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- async def test_device_active_opening(reverse_mode):
- """Test active opening."""
- 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, 0)
- )
- basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
- async def custom_implementation():
- return basic_info
- curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
- await curtain_device.get_basic_info()
- assert curtain_device.is_opening() is True
- assert curtain_device.is_closing() is False
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- async def test_device_active_closing(reverse_mode):
- """Test active closing."""
- 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, 100)
- )
- basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
- async def custom_implementation():
- return basic_info
- curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
- await curtain_device.get_basic_info()
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is True
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- async def test_device_active_opening_then_stop(reverse_mode):
- """Test active stopped after opening."""
- 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, 0)
- )
- basic_info = bytes([0, 0, 0, 0, 0, 67, 10, 0])
- async def custom_implementation():
- return basic_info
- curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
- await curtain_device.get_basic_info()
- basic_info = bytes([0, 0, 0, 0, 0, 0, 10, 0])
- await curtain_device.get_basic_info()
- assert curtain_device.is_opening() is False
- assert curtain_device.is_closing() is False
- @pytest.mark.asyncio
- @pytest.mark.parametrize("reverse_mode", [(True), (False)])
- async def test_device_active_closing_then_stop(reverse_mode):
- """Test active stopped after closing."""
- 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, 100)
- )
- basic_info = bytes([0, 0, 0, 0, 0, 67, 90, 0])
- async def custom_implementation():
- return basic_info
- curtain_device._get_basic_info = Mock(side_effect=custom_implementation)
- await curtain_device.get_basic_info()
- basic_info = bytes([0, 0, 0, 0, 0, 0, 90, 0])
- await curtain_device.get_basic_info()
- assert curtain_device.is_opening() 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
|