test_hysen_hy08we2_thermostat.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. from homeassistant.components.binary_sensor import (
  2. BinarySensorDeviceClass,
  3. )
  4. from homeassistant.components.climate.const import (
  5. ClimateEntityFeature,
  6. HVACAction,
  7. HVACMode,
  8. )
  9. from homeassistant.components.sensor import SensorDeviceClass
  10. from homeassistant.const import TIME_DAYS, UnitOfTemperature
  11. from ..const import HYSEN_HY08WE2_THERMOSTAT_PAYLOAD
  12. from ..helpers import assert_device_properties_set
  13. from ..mixins.binary_sensor import BasicBinarySensorTests
  14. from ..mixins.climate import TargetTemperatureTests
  15. from ..mixins.lock import BasicLockTests
  16. from ..mixins.number import MultiNumberTests
  17. from ..mixins.select import MultiSelectTests
  18. from ..mixins.sensor import BasicSensorTests
  19. from .base_device_tests import TuyaDeviceTestCase
  20. HVACMODE_DPS = "1"
  21. TEMPERATURE_DPS = "2"
  22. CURRENTTEMP_DPS = "3"
  23. PRESET_DPS = "4"
  24. LOCK_DPS = "6"
  25. ERROR_DPS = "12"
  26. UNIT_DPS = "101"
  27. HVACACTION_DPS = "102"
  28. EXTTEMP_DPS = "103"
  29. HOLIDAYS_DPS = "104"
  30. HOLIDAYTEMP_DPS = "105"
  31. UNKNOWN106_DPS = "106"
  32. UNKNOWN107_DPS = "107"
  33. DISPLAY_DPS = "108"
  34. CALIBOFFSET_DPS = "109"
  35. CALIBSWINGINT_DPS = "110"
  36. CALIBSWINGEXT_DPS = "111"
  37. HIGHTEMP_DPS = "112"
  38. LOWTEMP_DPS = "113"
  39. MAXTEMP_DPS = "114"
  40. MINTEMP_DPS = "115"
  41. SENSOR_DPS = "116"
  42. INITIAL_DPS = "117"
  43. SCHED_DPS = "118"
  44. class TestHysenHY08WE2Thermostat(
  45. BasicBinarySensorTests,
  46. BasicLockTests,
  47. BasicSensorTests,
  48. MultiNumberTests,
  49. MultiSelectTests,
  50. TargetTemperatureTests,
  51. TuyaDeviceTestCase,
  52. ):
  53. __test__ = True
  54. def setUp(self):
  55. self.setUpForConfig(
  56. "hysen_hy08we2_thermostat.yaml",
  57. HYSEN_HY08WE2_THERMOSTAT_PAYLOAD,
  58. )
  59. self.subject = self.entities.get("climate")
  60. self.setUpTargetTemperature(
  61. TEMPERATURE_DPS,
  62. self.subject,
  63. min=5.0,
  64. max=30.0,
  65. scale=10,
  66. step=5,
  67. )
  68. self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
  69. self.setUpBasicBinarySensor(
  70. ERROR_DPS,
  71. self.entities.get("binary_sensor_fault"),
  72. testdata=(1, 0),
  73. device_class=BinarySensorDeviceClass.PROBLEM,
  74. )
  75. self.setUpMultiSelect(
  76. [
  77. {
  78. "dps": SCHED_DPS,
  79. "name": "select_schedule",
  80. "options": {
  81. "2days": "5 + 2 day",
  82. "1days": "6 + 1 day",
  83. "0days": "7 day",
  84. },
  85. },
  86. {
  87. "dps": INITIAL_DPS,
  88. "name": "select_initial_state",
  89. "options": {
  90. "keep": "Previous",
  91. "on": "On",
  92. "off": "Off",
  93. },
  94. },
  95. {
  96. "dps": SENSOR_DPS,
  97. "name": "select_temperature_sensor",
  98. "options": {
  99. "in": "Internal",
  100. "ext": "External",
  101. "all": "Both",
  102. },
  103. },
  104. {
  105. "dps": UNIT_DPS,
  106. "name": "select_temperature_unit",
  107. "options": {
  108. False: "Celsius",
  109. True: "Fahrenheit",
  110. },
  111. },
  112. ],
  113. )
  114. self.setUpBasicSensor(
  115. EXTTEMP_DPS,
  116. self.entities.get("sensor_external_temperature"),
  117. device_class=SensorDeviceClass.TEMPERATURE,
  118. testdata=(205, 20.5),
  119. unit=UnitOfTemperature.CELSIUS,
  120. state_class="measurement",
  121. )
  122. self.setUpMultiNumber(
  123. [
  124. {
  125. "dps": HOLIDAYS_DPS,
  126. "name": "number_holiday_days",
  127. "min": 1,
  128. "max": 30,
  129. "unit": TIME_DAYS,
  130. },
  131. {
  132. "dps": HOLIDAYTEMP_DPS,
  133. "name": "number_holiday_temperature",
  134. "min": 5,
  135. "max": 30,
  136. "unit": UnitOfTemperature.CELSIUS,
  137. },
  138. {
  139. "dps": CALIBOFFSET_DPS,
  140. "name": "number_calibration_offset",
  141. "min": -9,
  142. "max": 9,
  143. "unit": UnitOfTemperature.CELSIUS,
  144. },
  145. {
  146. "dps": CALIBSWINGINT_DPS,
  147. "name": "number_calibration_swing_internal",
  148. "min": 0.5,
  149. "max": 2.5,
  150. "scale": 10,
  151. "step": 0.1,
  152. "unit": UnitOfTemperature.CELSIUS,
  153. },
  154. {
  155. "dps": CALIBSWINGEXT_DPS,
  156. "name": "number_calibration_swing_external",
  157. "min": 0.1,
  158. "max": 1.0,
  159. "scale": 10,
  160. "step": 0.1,
  161. "unit": UnitOfTemperature.CELSIUS,
  162. },
  163. {
  164. "dps": HIGHTEMP_DPS,
  165. "name": "number_high_temperature_protection",
  166. "min": 35,
  167. "max": 70,
  168. "unit": UnitOfTemperature.CELSIUS,
  169. },
  170. {
  171. "dps": LOWTEMP_DPS,
  172. "name": "number_low_temperature_protection",
  173. "min": 1,
  174. "max": 10,
  175. "unit": UnitOfTemperature.CELSIUS,
  176. },
  177. {
  178. "dps": MINTEMP_DPS,
  179. "name": "number_low_temperature_limit",
  180. "min": 1,
  181. "max": 10,
  182. "unit": UnitOfTemperature.CELSIUS,
  183. },
  184. {
  185. "dps": MAXTEMP_DPS,
  186. "name": "number_high_temperature_limit",
  187. "min": 2,
  188. "max": 70,
  189. "unit": UnitOfTemperature.CELSIUS,
  190. },
  191. ],
  192. )
  193. self.mark_secondary(
  194. [
  195. "binary_sensor_fault",
  196. "lock_child_lock",
  197. "number_holiday_days",
  198. "number_holiday_temperature",
  199. "number_calibration_offset",
  200. "number_calibration_swing_internal",
  201. "number_calibration_swing_external",
  202. "number_high_temperature_protection",
  203. "number_low_temperature_protection",
  204. "number_low_temperature_limit",
  205. "number_high_temperature_limit",
  206. "select_initial_state",
  207. "select_schedule",
  208. "select_temperature_sensor",
  209. "select_temperature_unit",
  210. ],
  211. )
  212. def test_supported_features(self):
  213. self.assertEqual(
  214. self.subject.supported_features,
  215. (
  216. ClimateEntityFeature.PRESET_MODE
  217. | ClimateEntityFeature.TARGET_TEMPERATURE
  218. ),
  219. )
  220. def test_current_temperature(self):
  221. self.dps[CURRENTTEMP_DPS] = 685
  222. self.assertEqual(self.subject.current_temperature, 68.5)
  223. def test_hvac_mode(self):
  224. self.dps[HVACMODE_DPS] = False
  225. self.assertEqual(self.subject.hvac_mode, HVACMode.OFF)
  226. self.dps[HVACMODE_DPS] = True
  227. self.assertEqual(self.subject.hvac_mode, HVACMode.HEAT)
  228. def test_hvac_modes(self):
  229. self.assertCountEqual(
  230. self.subject.hvac_modes,
  231. [
  232. HVACMode.HEAT,
  233. HVACMode.OFF,
  234. ],
  235. )
  236. async def test_set_hvac_mode_heat(self):
  237. async with assert_device_properties_set(
  238. self.subject._device,
  239. {HVACMODE_DPS: True},
  240. ):
  241. await self.subject.async_set_hvac_mode(HVACMode.HEAT)
  242. async def test_set_hvac_mode_off(self):
  243. async with assert_device_properties_set(
  244. self.subject._device,
  245. {HVACMODE_DPS: False},
  246. ):
  247. await self.subject.async_set_hvac_mode(HVACMode.OFF)
  248. def test_hvac_action(self):
  249. self.dps[HVACMODE_DPS] = True
  250. self.dps[HVACACTION_DPS] = True
  251. self.assertEqual(self.subject.hvac_action, HVACAction.HEATING)
  252. self.dps[HVACACTION_DPS] = False
  253. self.assertEqual(self.subject.hvac_action, HVACAction.IDLE)
  254. self.dps[HVACMODE_DPS] = False
  255. self.assertEqual(self.subject.hvac_action, HVACAction.OFF)
  256. def test_preset_modes(self):
  257. self.assertCountEqual(
  258. self.subject.preset_modes,
  259. ["Manual", "Program", "Program Override", "Holiday"],
  260. )
  261. def test_preset_mode(self):
  262. self.dps[PRESET_DPS] = "Manual"
  263. self.assertEqual(self.subject.preset_mode, "Manual")
  264. self.dps[PRESET_DPS] = "Program"
  265. self.assertEqual(self.subject.preset_mode, "Program")
  266. self.dps[PRESET_DPS] = "TempProg"
  267. self.assertEqual(self.subject.preset_mode, "Program Override")
  268. self.dps[PRESET_DPS] = "Holiday"
  269. self.assertEqual(self.subject.preset_mode, "Holiday")
  270. # Override - since min and max are set by attributes, the range
  271. # allowed when setting is wider than normal. The thermostat seems
  272. # to be configurable as at least a water heater (to 212F), as tuya
  273. # doc says max 1000.0 (after scaling)
  274. async def test_set_target_temperature_fails_outside_valid_range(self):
  275. with self.assertRaisesRegex(
  276. ValueError,
  277. f"temperature \\(0\\) must be between 0.5 and 122.0",
  278. ):
  279. await self.subject.async_set_target_temperature(0)
  280. with self.assertRaisesRegex(
  281. ValueError,
  282. f"temperature \\(122.5\\) must be between 0.5 and 122.0",
  283. ):
  284. await self.subject.async_set_target_temperature(122.5)
  285. def test_extra_state_attributes(self):
  286. self.dps[ERROR_DPS] = 12
  287. self.dps[UNKNOWN106_DPS] = False
  288. self.dps[UNKNOWN107_DPS] = True
  289. self.dps[DISPLAY_DPS] = True
  290. self.assertDictEqual(
  291. self.subject.extra_state_attributes,
  292. {
  293. "fault_code": 12,
  294. "unknown_106": False,
  295. "unknown_107": True,
  296. "temperature_display": "external",
  297. },
  298. )
  299. self.dps[DISPLAY_DPS] = False
  300. self.assertDictEqual(
  301. self.subject.extra_state_attributes,
  302. {
  303. "fault_code": 12,
  304. "unknown_106": False,
  305. "unknown_107": True,
  306. "temperature_display": "internal",
  307. },
  308. )
  309. def test_icons(self):
  310. self.dps[LOCK_DPS] = True
  311. self.assertEqual(self.basicLock.icon, "mdi:hand-back-right-off")
  312. self.dps[LOCK_DPS] = False
  313. self.assertEqual(self.basicLock.icon, "mdi:hand-back-right")