test_ceiling_light.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from unittest.mock import AsyncMock, MagicMock, patch
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotModel
  5. from switchbot.const.light import ColorMode
  6. from switchbot.devices import ceiling_light
  7. from .test_adv_parser import generate_ble_device
  8. def create_device_for_command_testing(
  9. init_data: dict | None = None, model: SwitchbotModel = SwitchbotModel.CEILING_LIGHT
  10. ):
  11. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  12. device = ceiling_light.SwitchbotCeilingLight(ble_device, model=model)
  13. device.update_from_advertisement(make_advertisement_data(ble_device, init_data))
  14. device._send_command = AsyncMock()
  15. device._check_command_result = MagicMock()
  16. device.update = AsyncMock()
  17. return device
  18. def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
  19. """Set advertisement data with defaults."""
  20. if init_data is None:
  21. init_data = {}
  22. return SwitchBotAdvertisement(
  23. address="aa:bb:cc:dd:ee:ff",
  24. data={
  25. "rawAdvData": b"q\x00",
  26. "data": {
  27. "brightness": 1,
  28. "color_mode": 1,
  29. "cw": 6387,
  30. "isOn": False,
  31. "sequence_number": 10,
  32. }
  33. | init_data,
  34. "isEncrypted": False,
  35. "model": b"q\x00",
  36. "modelFriendlyName": "Ceiling Light",
  37. "modelName": SwitchbotModel.CEILING_LIGHT,
  38. },
  39. device=ble_device,
  40. rssi=-80,
  41. active=True,
  42. )
  43. @pytest.mark.asyncio
  44. async def test_default_info():
  45. """Test default initialization of the ceiling light."""
  46. device = create_device_for_command_testing()
  47. assert device.rgb is None
  48. device._state = {"cw": 3200}
  49. assert device.is_on() is False
  50. assert device.on is False
  51. assert device.color_mode == ColorMode.COLOR_TEMP
  52. assert device.color_modes == {ColorMode.COLOR_TEMP}
  53. assert device.color_temp == 3200
  54. assert device.brightness == 1
  55. assert device.min_temp == 2700
  56. assert device.max_temp == 6500
  57. assert device.get_effect_list is None
  58. @pytest.mark.asyncio
  59. @pytest.mark.parametrize(
  60. ("basic_info", "version_info"), [(True, False), (False, True), (False, False)]
  61. )
  62. async def test_get_basic_info_returns_none(basic_info, version_info):
  63. device = create_device_for_command_testing()
  64. device._send_command = AsyncMock(side_effect=[version_info, basic_info])
  65. device._check_command_result = MagicMock(
  66. side_effect=[bool(version_info), bool(basic_info)]
  67. )
  68. assert await device.get_basic_info() is None
  69. @pytest.mark.asyncio
  70. @pytest.mark.parametrize(
  71. ("info_data", "result"),
  72. [
  73. (
  74. {
  75. "basic_info": b"\x01\x80=\x0f\xa1\x00\x01",
  76. "version_info": b"\x01d\x15\x0f\x00\x00\x00\x00\x00\x00\x00\n\x00",
  77. },
  78. [True, 61, 4001, 0, 2.1],
  79. ),
  80. (
  81. {
  82. "basic_info": b"\x01\x80\x0e\x12B\x00\x01",
  83. "version_info": b"\x01d\x15\x0f\x00\x00\x00\x00\x00\x00\x00\n\x00",
  84. },
  85. [True, 14, 4674, 0, 2.1],
  86. ),
  87. (
  88. {
  89. "basic_info": b"\x01\x00\x0e\x10\x96\x00\x01",
  90. "version_info": b"\x01d\x15\x0f\x00\x00\x00\x00\x00\x00\x00\n\x00",
  91. },
  92. [False, 14, 4246, 0, 2.1],
  93. ),
  94. pytest.param(
  95. {
  96. "basic_info": b"\x01\xc0\x0e\x10\x96\x00\x01",
  97. "version_info": b"\x01d\x15\x0f\x00\x00\x00\x00\x00\x00\x00\n\x00",
  98. },
  99. [True, 14, 4246, 1, 2.1],
  100. id="night-color-mode",
  101. ),
  102. ],
  103. )
  104. async def test_get_basic_info(info_data, result):
  105. """Test getting basic info from the ceiling light."""
  106. device = create_device_for_command_testing()
  107. device._send_command = AsyncMock(
  108. side_effect=[info_data["version_info"], info_data["basic_info"]]
  109. )
  110. device._check_command_result = MagicMock(side_effect=[True, True])
  111. info = await device.get_basic_info()
  112. assert info["isOn"] is result[0]
  113. assert info["brightness"] == result[1]
  114. assert info["cw"] == result[2]
  115. assert info["color_mode"] == result[3]
  116. assert info["firmware"] == result[4]
  117. @pytest.mark.asyncio
  118. async def test_get_basic_info_ignores_invalid_color_temp() -> None:
  119. """Test retaining the last color temp when basic info reports a placeholder."""
  120. device = create_device_for_command_testing()
  121. device._state["cw"] = 3625
  122. device._send_command = AsyncMock(
  123. side_effect=[
  124. b"\x01d\x18\x0e\x00\x00\x00\x00\x00\x00\x00\x0c\x01",
  125. b"\x01\x80F\xff\x00\x01\x00",
  126. ]
  127. )
  128. device._check_command_result = MagicMock(side_effect=[True, True])
  129. info = await device.get_basic_info()
  130. assert info is not None
  131. assert info["cw"] == 3625
  132. assert device.color_temp == 3625
  133. @pytest.mark.asyncio
  134. async def test_get_basic_info_uses_default_for_initial_invalid_color_temp() -> None:
  135. """Test using the default color temp for an initial placeholder."""
  136. device = create_device_for_command_testing()
  137. device._send_command = AsyncMock(
  138. side_effect=[
  139. b"\x01d\x18\x0e\x00\x00\x00\x00\x00\x00\x00\x0c\x01",
  140. b"\x01\x80F\xff\x00\x01\x00",
  141. ]
  142. )
  143. device._check_command_result = MagicMock(side_effect=[True, True])
  144. info = await device.get_basic_info()
  145. assert info is not None
  146. assert info["cw"] == 4001
  147. assert device.color_temp == 4001
  148. @pytest.mark.asyncio
  149. async def test_set_color_temp():
  150. """Test setting color temperature."""
  151. device = create_device_for_command_testing()
  152. await device.set_color_temp(50, 3000)
  153. device._send_command.assert_called_with(
  154. device._set_color_temp_command.format("320BB8")
  155. )
  156. @pytest.mark.asyncio
  157. async def test_turn_on():
  158. """Test turning on the ceiling light."""
  159. device = create_device_for_command_testing({"isOn": True})
  160. await device.turn_on()
  161. device._send_command.assert_called_with(device._turn_on_command)
  162. assert device.is_on() is True
  163. @pytest.mark.asyncio
  164. async def test_turn_off():
  165. """Test turning off the ceiling light."""
  166. device = create_device_for_command_testing({"isOn": False})
  167. await device.turn_off()
  168. device._send_command.assert_called_with(device._turn_off_command)
  169. assert device.is_on() is False
  170. @pytest.mark.asyncio
  171. async def test_set_brightness():
  172. """Test setting brightness."""
  173. device = create_device_for_command_testing()
  174. await device.set_brightness(75)
  175. device._send_command.assert_called_with(
  176. device._set_brightness_command.format("4B0FA1")
  177. )
  178. @pytest.mark.asyncio
  179. @pytest.mark.parametrize(
  180. ("adv_value", "expected_color_mode"),
  181. [
  182. (0, ColorMode.COLOR_TEMP),
  183. (1, ColorMode.COLOR_TEMP),
  184. (4, ColorMode.EFFECT),
  185. (10, ColorMode.OFF),
  186. (None, ColorMode.OFF),
  187. ],
  188. )
  189. async def test_get_color_mode(adv_value, expected_color_mode):
  190. """Test getting color mode."""
  191. device = create_device_for_command_testing()
  192. with patch.object(device, "_get_adv_value", return_value=adv_value):
  193. assert device.color_mode == expected_color_mode