test_smart_thermostat_radiator.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from unittest.mock import AsyncMock, MagicMock, patch
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement
  5. from switchbot.const.climate import ClimateAction, ClimateMode
  6. from switchbot.const.climate import SmartThermostatRadiatorMode as STRMode
  7. from switchbot.devices.device import SwitchbotEncryptedDevice, SwitchbotOperationError
  8. from switchbot.devices.smart_thermostat_radiator import (
  9. COMMAND_SET_MODE,
  10. COMMAND_SET_TEMP,
  11. SwitchbotSmartThermostatRadiator,
  12. )
  13. from . import SMART_THERMOSTAT_RADIATOR_INFO
  14. from .test_adv_parser import AdvTestCase, generate_ble_device
  15. def create_device_for_command_testing(
  16. adv_info: AdvTestCase,
  17. init_data: dict | None = None,
  18. ):
  19. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  20. device = SwitchbotSmartThermostatRadiator(
  21. ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=adv_info.modelName
  22. )
  23. device.update_from_advertisement(
  24. make_advertisement_data(ble_device, adv_info, init_data)
  25. )
  26. device._send_command = AsyncMock()
  27. device._check_command_result = MagicMock()
  28. device.update = AsyncMock()
  29. return device
  30. def make_advertisement_data(
  31. ble_device: BLEDevice, adv_info: AdvTestCase, init_data: dict | None = None
  32. ):
  33. """Set advertisement data with defaults."""
  34. if init_data is None:
  35. init_data = {}
  36. return SwitchBotAdvertisement(
  37. address="aa:bb:cc:dd:ee:ff",
  38. data={
  39. "rawAdvData": adv_info.service_data,
  40. "data": adv_info.data | init_data,
  41. "isEncrypted": False,
  42. "model": adv_info.model,
  43. "modelFriendlyName": adv_info.modelFriendlyName,
  44. "modelName": adv_info.modelName,
  45. }
  46. | init_data,
  47. device=ble_device,
  48. rssi=-80,
  49. active=True,
  50. )
  51. @pytest.mark.asyncio
  52. async def test_default_info() -> None:
  53. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  54. assert device.min_temperature == 5.0
  55. assert device.max_temperature == 35.0
  56. assert device.preset_mode == STRMode.MANUAL.lname
  57. assert device.preset_modes == STRMode.get_modes()
  58. assert device.hvac_mode == ClimateMode.HEAT
  59. assert device.hvac_modes == {ClimateMode.OFF, ClimateMode.HEAT}
  60. assert device.hvac_action == ClimateAction.HEATING
  61. assert device.target_temperature == 35.0
  62. assert device.current_temperature == 28.0
  63. assert device.door_open() is False
  64. @pytest.mark.asyncio
  65. async def test_default_info_with_off_mode() -> None:
  66. device = create_device_for_command_testing(
  67. SMART_THERMOSTAT_RADIATOR_INFO, {"mode": STRMode.OFF.lname, "isOn": False}
  68. )
  69. assert device.hvac_action == ClimateAction.OFF
  70. @pytest.mark.parametrize(
  71. ("mode", "expected_command"),
  72. [
  73. (ClimateMode.OFF, "570100"),
  74. (ClimateMode.HEAT, COMMAND_SET_MODE[STRMode.COMFORT.lname]),
  75. ],
  76. )
  77. @pytest.mark.asyncio
  78. async def test_set_hvac_mode_commands(mode, expected_command) -> None:
  79. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  80. await device.set_hvac_mode(mode)
  81. device._send_command.assert_awaited_with(expected_command)
  82. @pytest.mark.parametrize(
  83. ("preset_mode", "expected_command"),
  84. [
  85. (STRMode.SCHEDULE.lname, COMMAND_SET_MODE[STRMode.SCHEDULE.lname]),
  86. (STRMode.MANUAL.lname, COMMAND_SET_MODE[STRMode.MANUAL.lname]),
  87. (STRMode.OFF.lname, COMMAND_SET_MODE[STRMode.OFF.lname]),
  88. (STRMode.ECONOMIC.lname, COMMAND_SET_MODE[STRMode.ECONOMIC.lname]),
  89. (STRMode.COMFORT.lname, COMMAND_SET_MODE[STRMode.COMFORT.lname]),
  90. (STRMode.FAST_HEATING.lname, COMMAND_SET_MODE[STRMode.FAST_HEATING.lname]),
  91. ],
  92. )
  93. @pytest.mark.asyncio
  94. async def test_set_preset_mode_commands(preset_mode, expected_command) -> None:
  95. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  96. await device.set_preset_mode(preset_mode)
  97. device._send_command.assert_awaited_with(expected_command)
  98. @pytest.mark.asyncio
  99. async def test_set_target_temperature_command() -> None:
  100. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  101. await device.set_target_temperature(22.5)
  102. device._send_command.assert_awaited_with(
  103. COMMAND_SET_TEMP[STRMode.MANUAL.lname].format(temp=225)
  104. )
  105. @pytest.mark.asyncio
  106. @pytest.mark.parametrize(
  107. ("mode", "match"),
  108. [
  109. (STRMode.OFF.lname, "Cannot set temperature when mode is OFF."),
  110. (STRMode.FAST_HEATING.lname, "Fast Heating mode defaults to max temperature."),
  111. ],
  112. )
  113. async def test_set_target_temperature_with_invalid_mode(mode, match) -> None:
  114. device = create_device_for_command_testing(
  115. SMART_THERMOSTAT_RADIATOR_INFO, {"mode": mode}
  116. )
  117. with pytest.raises(SwitchbotOperationError, match=match):
  118. await device.set_target_temperature(22.5)
  119. @pytest.mark.asyncio
  120. async def test_get_basic_info_none() -> None:
  121. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  122. device._get_basic_info = AsyncMock(return_value=None)
  123. assert await device.get_basic_info() is None
  124. @pytest.mark.asyncio
  125. @pytest.mark.parametrize(
  126. ("basic_info", "result"),
  127. [
  128. (
  129. b"\x01d\x08>\x14\x80\xe6\x00(\x82\xbe\x00T\x00\x82\x00\x00",
  130. [
  131. 100,
  132. 0.8,
  133. 62,
  134. "off",
  135. "comfort",
  136. 23.0,
  137. 4.0,
  138. 13.0,
  139. 19.0,
  140. 0,
  141. False,
  142. 13.0,
  143. False,
  144. ],
  145. ),
  146. (
  147. b"\x01d\x08>#\x80\xf0\x00(\x82\xbe\x00T\x00\x82\x00\x00",
  148. [
  149. 100,
  150. 0.8,
  151. 62,
  152. "comfort",
  153. "economic",
  154. 24.0,
  155. 4.0,
  156. 13.0,
  157. 19.0,
  158. 0,
  159. False,
  160. 13.0,
  161. False,
  162. ],
  163. ),
  164. ],
  165. )
  166. async def test_get_basic_info_parsing(basic_info, result) -> None:
  167. device = create_device_for_command_testing(SMART_THERMOSTAT_RADIATOR_INFO)
  168. device._get_basic_info = AsyncMock(return_value=basic_info)
  169. info = await device.get_basic_info()
  170. assert info["battery"] == result[0]
  171. assert info["firmware"] == result[1]
  172. assert info["hardware"] == result[2]
  173. assert info["last_mode"] == result[3]
  174. assert info["mode"] == result[4]
  175. assert info["temperature"] == result[5]
  176. assert info["manual_target_temp"] == result[6]
  177. assert info["comfort_target_temp"] == result[7]
  178. assert info["economic_target_temp"] == result[8]
  179. assert info["fast_heat_time"] == result[9]
  180. assert info["child_lock"] == result[10]
  181. assert info["target_temp"] == result[11]
  182. assert info["door_open"] == result[12]
  183. @pytest.mark.asyncio
  184. @patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
  185. async def test_verify_encryption_key(mock_parent_verify):
  186. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  187. key_id = "ff"
  188. encryption_key = "ffffffffffffffffffffffffffffffff"
  189. mock_parent_verify.return_value = True
  190. result = await SwitchbotSmartThermostatRadiator.verify_encryption_key(
  191. device=ble_device,
  192. key_id=key_id,
  193. encryption_key=encryption_key,
  194. model=SMART_THERMOSTAT_RADIATOR_INFO.modelName,
  195. )
  196. mock_parent_verify.assert_awaited_once_with(
  197. ble_device,
  198. key_id,
  199. encryption_key,
  200. SMART_THERMOSTAT_RADIATOR_INFO.modelName,
  201. )
  202. assert result is True