test_hysen_hy08we2_thermostat.py 12 KB

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