test_bulb.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from unittest.mock import AsyncMock, MagicMock
  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 bulb
  7. from switchbot.devices.device import SwitchbotOperationError
  8. from .test_adv_parser import generate_ble_device
  9. def create_device_for_command_testing(
  10. init_data: dict | None = None, model: SwitchbotModel = SwitchbotModel.COLOR_BULB
  11. ):
  12. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  13. device = bulb.SwitchbotBulb(ble_device, model=model)
  14. device.update_from_advertisement(make_advertisement_data(ble_device, init_data))
  15. device._send_command = AsyncMock()
  16. device._check_command_result = MagicMock()
  17. device.update = AsyncMock()
  18. return device
  19. def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
  20. """Set advertisement data with defaults."""
  21. if init_data is None:
  22. init_data = {}
  23. return SwitchBotAdvertisement(
  24. address="aa:bb:cc:dd:ee:ff",
  25. data={
  26. "rawAdvData": b"u\x00d",
  27. "data": {
  28. "brightness": 1,
  29. "color_mode": 2,
  30. "delay": False,
  31. "isOn": True,
  32. "loop_index": 0,
  33. "preset": False,
  34. "sequence_number": 2,
  35. "speed": 0,
  36. }
  37. | init_data,
  38. "isEncrypted": False,
  39. "model": "u",
  40. "modelFriendlyName": "Color Bulb",
  41. "modelName": SwitchbotModel.COLOR_BULB,
  42. },
  43. device=ble_device,
  44. rssi=-80,
  45. active=True,
  46. )
  47. @pytest.mark.asyncio
  48. async def test_default_info():
  49. """Test default initialization of the color bulb."""
  50. device = create_device_for_command_testing()
  51. assert device.rgb is None
  52. device._state = {"r": 30, "g": 0, "b": 0, "cw": 3200}
  53. assert device.is_on() is True
  54. assert device.on is True
  55. assert device.color_mode == ColorMode.RGB
  56. assert device.color_modes == {ColorMode.RGB, ColorMode.COLOR_TEMP}
  57. assert device.rgb == (30, 0, 0)
  58. assert device.color_temp == 3200
  59. assert device.brightness == 1
  60. assert device.min_temp == 2700
  61. assert device.max_temp == 6500
  62. assert device.get_effect_list == list(device._effect_dict.keys())
  63. @pytest.mark.asyncio
  64. @pytest.mark.parametrize(
  65. ("basic_info", "version_info"), [(True, False), (False, True), (False, False)]
  66. )
  67. async def test_get_basic_info_returns_none(basic_info, version_info):
  68. device = create_device_for_command_testing()
  69. async def mock_get_basic_info(arg):
  70. if arg == device._get_basic_info_command[1]:
  71. return basic_info
  72. if arg == device._get_basic_info_command[0]:
  73. return version_info
  74. return None
  75. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  76. assert await device.get_basic_info() is None
  77. @pytest.mark.asyncio
  78. @pytest.mark.parametrize(
  79. ("info_data", "result"),
  80. [
  81. (
  82. {
  83. "basic_info": b"\x01\x80\x01\xff\x91\x96\x00\x00\xff\xff\x02",
  84. "version_info": b"\x01\x01\x11",
  85. },
  86. [True, 1, 255, 145, 150, 0, 2, 1.7],
  87. ),
  88. (
  89. {
  90. "basic_info": b"\x01\x80;\x00\x00\x00\x0c\x99\xff\xff\x01",
  91. "version_info": b"\x01\x01\x11",
  92. },
  93. [True, 59, 0, 0, 0, 3225, 1, 1.7],
  94. ),
  95. (
  96. {
  97. "basic_info": b"\x01\x80\t!7\xff\x00\x00\xff\xff\x02",
  98. "version_info": b"\x01\x01\x11",
  99. },
  100. [True, 9, 33, 55, 255, 0, 2, 1.7],
  101. ),
  102. ],
  103. )
  104. async def test_get_basic_info(info_data, result):
  105. """Test getting basic info from the color bulb."""
  106. device = create_device_for_command_testing()
  107. async def mock_get_basic_info(args: str) -> list[int] | None:
  108. if args == device._get_basic_info_command[1]:
  109. return info_data["basic_info"]
  110. if args == device._get_basic_info_command[0]:
  111. return info_data["version_info"]
  112. return None
  113. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  114. info = await device.get_basic_info()
  115. assert info["isOn"] is result[0]
  116. assert info["brightness"] == result[1]
  117. assert info["r"] == result[2]
  118. assert info["g"] == result[3]
  119. assert info["b"] == result[4]
  120. assert info["cw"] == result[5]
  121. assert info["color_mode"] == result[6]
  122. assert info["firmware"] == result[7]
  123. @pytest.mark.asyncio
  124. async def test_set_color_temp():
  125. """Test setting color temperature."""
  126. device = create_device_for_command_testing()
  127. await device.set_color_temp(50, 3000)
  128. device._send_command.assert_called_with(
  129. device._set_color_temp_command.format("320BB8")
  130. )
  131. @pytest.mark.asyncio
  132. async def test_turn_on():
  133. """Test turning on the color bulb."""
  134. device = create_device_for_command_testing({"isOn": True})
  135. await device.turn_on()
  136. device._send_command.assert_called_with(device._turn_on_command)
  137. assert device.is_on() is True
  138. @pytest.mark.asyncio
  139. async def test_turn_off():
  140. """Test turning off the color bulb."""
  141. device = create_device_for_command_testing({"isOn": False})
  142. await device.turn_off()
  143. device._send_command.assert_called_with(device._turn_off_command)
  144. assert device.is_on() is False
  145. @pytest.mark.asyncio
  146. async def test_set_brightness():
  147. """Test setting brightness."""
  148. device = create_device_for_command_testing()
  149. await device.set_brightness(75)
  150. device._send_command.assert_called_with(device._set_brightness_command.format("4B"))
  151. @pytest.mark.asyncio
  152. async def test_set_rgb():
  153. """Test setting RGB values."""
  154. device = create_device_for_command_testing()
  155. await device.set_rgb(100, 255, 128, 64)
  156. device._send_command.assert_called_with(device._set_rgb_command.format("64FF8040"))
  157. @pytest.mark.asyncio
  158. async def test_set_effect_with_invalid_effect():
  159. """Test setting an invalid effect."""
  160. device = create_device_for_command_testing()
  161. with pytest.raises(
  162. SwitchbotOperationError, match="Effect invalid_effect not supported"
  163. ):
  164. await device.set_effect("invalid_effect")
  165. @pytest.mark.asyncio
  166. async def test_set_effect_with_valid_effect():
  167. """Test setting a valid effect."""
  168. device = create_device_for_command_testing()
  169. await device.set_effect("Colorful")
  170. device._send_command.assert_called_with(device._effect_dict["Colorful"][0])
  171. assert device.get_effect() == "Colorful"