1
0

test_blind_tilt.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from unittest.mock import AsyncMock
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotModel
  5. from switchbot.devices import blind_tilt
  6. from switchbot.devices.base_cover import COVER_EXT_SUM_KEY
  7. from .test_adv_parser import generate_ble_device
  8. def create_device_for_command_testing(
  9. position=50, calibration=True, reverse_mode=False
  10. ):
  11. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  12. curtain_device = blind_tilt.SwitchbotBlindTilt(
  13. ble_device, reverse_mode=reverse_mode
  14. )
  15. curtain_device.update_from_advertisement(
  16. make_advertisement_data(ble_device, True, position, calibration)
  17. )
  18. curtain_device._send_multiple_commands = AsyncMock()
  19. curtain_device.update = AsyncMock()
  20. return curtain_device
  21. def make_advertisement_data(
  22. ble_device: BLEDevice, in_motion: bool, position: int, calibration: bool = True
  23. ):
  24. """Set advertisement data with defaults."""
  25. return SwitchBotAdvertisement(
  26. address="aa:bb:cc:dd:ee:ff",
  27. data={
  28. "rawAdvData": b"c\xc0X\x00\x11\x04",
  29. "data": {
  30. "calibration": calibration,
  31. "battery": 88,
  32. "inMotion": in_motion,
  33. "tilt": position,
  34. "lightLevel": 1,
  35. "deviceChain": 1,
  36. },
  37. "isEncrypted": False,
  38. "model": "c",
  39. "modelFriendlyName": "Curtain",
  40. "modelName": SwitchbotModel.CURTAIN,
  41. },
  42. device=ble_device,
  43. rssi=-80,
  44. active=True,
  45. )
  46. @pytest.mark.asyncio
  47. async def test_open():
  48. blind_device = create_device_for_command_testing()
  49. await blind_device.open()
  50. blind_device._send_multiple_commands.assert_awaited_once_with(blind_tilt.OPEN_KEYS)
  51. @pytest.mark.asyncio
  52. @pytest.mark.parametrize(
  53. ("position", "keys"),
  54. [(5, blind_tilt.CLOSE_DOWN_KEYS), (55, blind_tilt.CLOSE_UP_KEYS)],
  55. )
  56. async def test_close(position, keys):
  57. blind_device = create_device_for_command_testing(position=position)
  58. await blind_device.close()
  59. blind_device._send_multiple_commands.assert_awaited_once_with(keys)
  60. @pytest.mark.asyncio
  61. async def test_get_basic_info_returns_none_when_no_data():
  62. blind_device = create_device_for_command_testing()
  63. blind_device._get_basic_info = AsyncMock(return_value=None)
  64. assert await blind_device.get_basic_info() is None
  65. @pytest.mark.asyncio
  66. @pytest.mark.parametrize(
  67. ("reverse_mode", "data", "result"),
  68. [
  69. (
  70. False,
  71. bytes([0, 1, 10, 2, 255, 255, 50, 4]),
  72. [1, 1, 1, 1, 1, True, False, False, True, 50, 4],
  73. ),
  74. (
  75. False,
  76. bytes([0, 1, 10, 2, 0, 0, 50, 4]),
  77. [1, 1, 0, 0, 0, False, False, False, False, 50, 4],
  78. ),
  79. (
  80. False,
  81. bytes([0, 1, 10, 2, 0, 1, 50, 4]),
  82. [1, 1, 0, 0, 1, False, True, False, True, 50, 4],
  83. ),
  84. (
  85. True,
  86. bytes([0, 1, 10, 2, 255, 255, 50, 4]),
  87. [1, 1, 1, 1, 1, True, False, True, False, 50, 4],
  88. ),
  89. (
  90. True,
  91. bytes([0, 1, 10, 2, 0, 0, 50, 4]),
  92. [1, 1, 0, 0, 0, False, False, False, False, 50, 4],
  93. ),
  94. (
  95. True,
  96. bytes([0, 1, 10, 2, 0, 1, 50, 4]),
  97. [1, 1, 0, 0, 1, False, True, False, True, 50, 4],
  98. ),
  99. ],
  100. )
  101. async def test_get_basic_info(reverse_mode, data, result):
  102. blind_device = create_device_for_command_testing(reverse_mode=reverse_mode)
  103. blind_device._get_basic_info = AsyncMock(return_value=data)
  104. info = await blind_device.get_basic_info()
  105. assert info["battery"] == result[0]
  106. assert info["firmware"] == result[1]
  107. assert info["light"] == result[2]
  108. assert info["fault"] == result[2]
  109. assert info["solarPanel"] == result[3]
  110. assert info["calibration"] == result[3]
  111. assert info["calibrated"] == result[3]
  112. assert info["inMotion"] == result[4]
  113. assert info["motionDirection"]["opening"] == result[5]
  114. assert info["motionDirection"]["closing"] == result[6]
  115. assert info["motionDirection"]["up"] == result[7]
  116. assert info["motionDirection"]["down"] == result[8]
  117. assert info["tilt"] == result[9]
  118. assert info["timers"] == result[10]
  119. @pytest.mark.asyncio
  120. async def test_get_extended_info_summary_sends_command():
  121. blind_device = create_device_for_command_testing()
  122. blind_device._send_command = AsyncMock()
  123. await blind_device.get_extended_info_summary()
  124. blind_device._send_command.assert_awaited_once_with(key=COVER_EXT_SUM_KEY)
  125. @pytest.mark.asyncio
  126. @pytest.mark.parametrize("data_value", [(None), (b"\x07"), (b"\x00")])
  127. async def test_get_extended_info_summary_returns_none_when_bad_data(data_value):
  128. blind_device = create_device_for_command_testing()
  129. blind_device._send_command = AsyncMock(return_value=data_value)
  130. assert await blind_device.get_extended_info_summary() is None
  131. @pytest.mark.asyncio
  132. @pytest.mark.parametrize(
  133. ("data", "result"), [(bytes([0, 0]), False), (bytes([0, 255]), True)]
  134. )
  135. async def test_get_extended_info_summary(data, result):
  136. blind_device = create_device_for_command_testing()
  137. blind_device._send_command = AsyncMock(return_value=data)
  138. ext_result = await blind_device.get_extended_info_summary()
  139. assert ext_result["device0"]["light"] == result
  140. @pytest.mark.parametrize("reverse_mode", [(True), (False)])
  141. def test_device_passive_opening(reverse_mode):
  142. """Test passive opening advertisement."""
  143. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  144. curtain_device = blind_tilt.SwitchbotBlindTilt(
  145. ble_device, reverse_mode=reverse_mode
  146. )
  147. curtain_device.update_from_advertisement(
  148. make_advertisement_data(ble_device, True, 0)
  149. )
  150. curtain_device.update_from_advertisement(
  151. make_advertisement_data(ble_device, True, 10)
  152. )
  153. assert curtain_device.is_opening() is True
  154. assert curtain_device.is_closing() is False
  155. @pytest.mark.parametrize("reverse_mode", [(True), (False)])
  156. def test_device_passive_closing(reverse_mode):
  157. """Test passive closing advertisement."""
  158. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  159. curtain_device = blind_tilt.SwitchbotBlindTilt(
  160. ble_device, reverse_mode=reverse_mode
  161. )
  162. curtain_device.update_from_advertisement(
  163. make_advertisement_data(ble_device, True, 100)
  164. )
  165. curtain_device.update_from_advertisement(
  166. make_advertisement_data(ble_device, True, 90)
  167. )
  168. assert curtain_device.is_opening() is False
  169. assert curtain_device.is_closing() is True
  170. @pytest.mark.parametrize("reverse_mode", [(True), (False)])
  171. def test_device_passive_opening_then_stop(reverse_mode):
  172. """Test passive stopped after opening advertisement."""
  173. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  174. curtain_device = blind_tilt.SwitchbotBlindTilt(
  175. ble_device, reverse_mode=reverse_mode
  176. )
  177. curtain_device.update_from_advertisement(
  178. make_advertisement_data(ble_device, True, 0)
  179. )
  180. curtain_device.update_from_advertisement(
  181. make_advertisement_data(ble_device, True, 10)
  182. )
  183. curtain_device.update_from_advertisement(
  184. make_advertisement_data(ble_device, False, 10)
  185. )
  186. assert curtain_device.is_opening() is False
  187. assert curtain_device.is_closing() is False
  188. @pytest.mark.parametrize("reverse_mode", [(True), (False)])
  189. def test_device_passive_closing_then_stop(reverse_mode):
  190. """Test passive stopped after closing advertisement."""
  191. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  192. curtain_device = blind_tilt.SwitchbotBlindTilt(
  193. ble_device, reverse_mode=reverse_mode
  194. )
  195. curtain_device.update_from_advertisement(
  196. make_advertisement_data(ble_device, True, 100)
  197. )
  198. curtain_device.update_from_advertisement(
  199. make_advertisement_data(ble_device, True, 90)
  200. )
  201. curtain_device.update_from_advertisement(
  202. make_advertisement_data(ble_device, False, 90)
  203. )
  204. assert curtain_device.is_opening() is False
  205. assert curtain_device.is_closing() is False