climate.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. """
  2. Platform to control tuya climate devices.
  3. """
  4. import logging
  5. from homeassistant.components.climate import ClimateEntity
  6. from homeassistant.components.climate.const import (
  7. ATTR_CURRENT_HUMIDITY,
  8. ATTR_CURRENT_TEMPERATURE,
  9. ATTR_FAN_MODE,
  10. ATTR_HUMIDITY,
  11. ATTR_PRESET_MODE,
  12. ATTR_SWING_MODE,
  13. ATTR_TARGET_TEMP_HIGH,
  14. ATTR_TARGET_TEMP_LOW,
  15. DEFAULT_MAX_HUMIDITY,
  16. DEFAULT_MAX_TEMP,
  17. DEFAULT_MIN_HUMIDITY,
  18. DEFAULT_MIN_TEMP,
  19. HVAC_MODE_AUTO,
  20. HVAC_MODE_OFF,
  21. SUPPORT_FAN_MODE,
  22. SUPPORT_PRESET_MODE,
  23. SUPPORT_SWING_MODE,
  24. SUPPORT_TARGET_HUMIDITY,
  25. SUPPORT_TARGET_TEMPERATURE,
  26. SUPPORT_TARGET_TEMPERATURE_RANGE,
  27. )
  28. from homeassistant.const import (
  29. ATTR_TEMPERATURE,
  30. STATE_UNAVAILABLE,
  31. TEMP_CELSIUS,
  32. TEMP_FAHRENHEIT,
  33. TEMP_KELVIN,
  34. )
  35. from ..device import TuyaLocalDevice
  36. from ..helpers.device_config import TuyaEntityConfig
  37. _LOGGER = logging.getLogger(__name__)
  38. class TuyaLocalClimate(ClimateEntity):
  39. """Representation of a Tuya Climate entity."""
  40. def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
  41. """
  42. Initialise the climate device.
  43. Args:
  44. device (TuyaLocalDevice): The device API instance.
  45. config (TuyaEntityConfig): The entity config.
  46. """
  47. self._device = device
  48. self._config = config
  49. self._support_flags = 0
  50. self._attr_dps = []
  51. dps_map = {c.name: c for c in config.dps()}
  52. self._current_temperature_dps = dps_map.pop(ATTR_CURRENT_TEMPERATURE, None)
  53. self._temperature_dps = dps_map.pop(ATTR_TEMPERATURE, None)
  54. self._temp_high_dps = dps_map.pop(ATTR_TARGET_TEMP_HIGH, None)
  55. self._temp_low_dps = dps_map.pop(ATTR_TARGET_TEMP_LOW, None)
  56. self._current_humidity_dps = dps_map.pop(ATTR_CURRENT_HUMIDITY, None)
  57. self._humidity_dps = dps_map.pop(ATTR_HUMIDITY, None)
  58. self._preset_mode_dps = dps_map.pop(ATTR_PRESET_MODE, None)
  59. self._swing_mode_dps = dps_map.pop(ATTR_SWING_MODE, None)
  60. self._fan_mode_dps = dps_map.pop(ATTR_FAN_MODE, None)
  61. self._hvac_mode_dps = dps_map.pop("hvac_mode", None)
  62. self._unit_dps = dps_map.pop("temperature_unit", None)
  63. for d in dps_map.values():
  64. if not d.hidden:
  65. self._attr_dps.append(d)
  66. if self._humidity_dps:
  67. self._support_flags |= SUPPORT_TARGET_HUMIDITY
  68. if self._preset_mode_dps:
  69. self._support_flags |= SUPPORT_PRESET_MODE
  70. if self._swing_mode_dps:
  71. self._support_flags |= SUPPORT_SWING_MODE
  72. if self._fan_mode_dps:
  73. self._support_flags |= SUPPORT_FAN_MODE
  74. if self._temp_high_dps and self._temp_low_dps:
  75. self._support_flags |= SUPPORT_TARGET_TEMPERATURE_RANGE
  76. elif self._temperature_dps is not None:
  77. self._support_flags |= SUPPORT_TARGET_TEMPERATURE
  78. @property
  79. def supported_features(self):
  80. """Return the features supported by this climate device."""
  81. return self._support_flags
  82. @property
  83. def should_poll(self):
  84. """Return the polling state."""
  85. return True
  86. @property
  87. def name(self):
  88. """Return the name of the climate device."""
  89. return self._device.name
  90. @property
  91. def friendly_name(self):
  92. """Return the friendly name of the climate entity for the UI."""
  93. return self._config.name
  94. @property
  95. def unique_id(self):
  96. """Return the unique id for this climate device."""
  97. return self._device.unique_id
  98. @property
  99. def device_info(self):
  100. """Return device information about this heater."""
  101. return self._device.device_info
  102. @property
  103. def icon(self):
  104. """Return the icon to use in the frontend for this device."""
  105. icon = self._config.icon(self._device)
  106. if icon:
  107. return icon
  108. else:
  109. return super().icon
  110. @property
  111. def temperature_unit(self):
  112. """Return the unit of measurement."""
  113. if self._unit_dps is not None:
  114. unit = self._unit_dps.get_value(self._device)
  115. # Only return valid units
  116. if unit == "C":
  117. return TEMP_CELSIUS
  118. elif unit == "F":
  119. return TEMP_FAHRENHEIT
  120. elif unit == "K":
  121. return TEMP_KELVIN
  122. return self._device.temperature_unit
  123. @property
  124. def target_temperature(self):
  125. """Return the currently set target temperature."""
  126. if self._temperature_dps is None:
  127. raise NotImplementedError()
  128. return self._temperature_dps.get_value(self._device)
  129. @property
  130. def target_temperature_high(self):
  131. """Return the currently set high target temperature."""
  132. if self._temp_high_dps is None:
  133. raise NotImplementedError()
  134. return self._temp_high_dps.get_value(self._device)
  135. @property
  136. def target_temperature_low(self):
  137. """Return the currently set low target temperature."""
  138. if self._temp_low_dps is None:
  139. raise NotImplementedError()
  140. return self._temp_low_dps.get_value(self._device)
  141. @property
  142. def target_temperature_step(self):
  143. """Return the supported step of target temperature."""
  144. dps = self._temperature_dps
  145. if dps is None:
  146. dps = self._temp_high_dps
  147. if dps is None:
  148. dps = self._temp_low_dps
  149. if dps is None:
  150. return 1
  151. return dps.step(self._device)
  152. @property
  153. def min_temp(self):
  154. """Return the minimum supported target temperature."""
  155. if self._temperature_dps is None:
  156. return None
  157. r = self._temperature_dps.range(self._device)
  158. return DEFAULT_MIN_TEMP if r is None else r["min"]
  159. @property
  160. def max_temp(self):
  161. """Return the maximum supported target temperature."""
  162. if self._temperature_dps is None:
  163. return None
  164. r = self._temperature_dps.range(self._device)
  165. return DEFAULT_MAX_TEMP if r is None else r["max"]
  166. async def async_set_temperature(self, **kwargs):
  167. """Set new target temperature."""
  168. if kwargs.get(ATTR_PRESET_MODE) is not None:
  169. await self.async_set_preset_mode(kwargs.get(ATTR_PRESET_MODE))
  170. if kwargs.get(ATTR_TEMPERATURE) is not None:
  171. await self.async_set_target_temperature(kwargs.get(ATTR_TEMPERATURE))
  172. high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
  173. low = kwargs.get(ATTR_TARGET_TEMP_LOW)
  174. if high is not None or low is not None:
  175. await self.async_set_target_temperature_range(low, high)
  176. async def async_set_target_temperature(self, target_temperature):
  177. if self._temperature_dps is None:
  178. raise NotImplementedError()
  179. await self._temperature_dps.async_set_value(self._device, target_temperature)
  180. async def async_set_target_temperature_range(self, low, high):
  181. """Set the target temperature range."""
  182. dps_map = {}
  183. if low is not None and self._temp_low_dps is not None:
  184. dps_map.update(self._temp_low_dps.get_values_to_set(self._device, low))
  185. if high is not None and self._temp_high_dps is not None:
  186. dps_map.update(self._temp_high_dps.get_values_to_set(self._device, high))
  187. if dps_map:
  188. await self._device.async_set_properties(dps_map)
  189. @property
  190. def current_temperature(self):
  191. """Return the current measured temperature."""
  192. if self._current_temperature_dps is None:
  193. return None
  194. return self._current_temperature_dps.get_value(self._device)
  195. @property
  196. def target_humidity(self):
  197. """Return the currently set target humidity."""
  198. if self._humidity_dps is None:
  199. raise NotImplementedError()
  200. return self._humidity_dps.get_value(self._device)
  201. @property
  202. def min_humidity(self):
  203. """Return the minimum supported target humidity."""
  204. if self._humidity_dps is None:
  205. return None
  206. r = self._humidity_dps.range(self._device)
  207. return DEFAULT_MIN_HUMIDITY if r is None else r["min"]
  208. @property
  209. def max_humidity(self):
  210. """Return the maximum supported target humidity."""
  211. if self._humidity_dps is None:
  212. return None
  213. r = self._humidity_dps.range(self._device)
  214. return DEFAULT_MAX_HUMIDITY if r is None else r["max"]
  215. async def async_set_humidity(self, target_humidity):
  216. if self._humidity_dps is None:
  217. raise NotImplementedError()
  218. await self._humidity_dps.async_set_value(self._device, target_humidity)
  219. @property
  220. def current_humidity(self):
  221. """Return the current measured humidity."""
  222. if self._current_humidity_dps is None:
  223. return None
  224. return self._current_humidity_dps.get_value(self._device)
  225. @property
  226. def hvac_mode(self):
  227. """Return current HVAC mode."""
  228. if self._hvac_mode_dps is None:
  229. return HVAC_MODE_AUTO
  230. hvac_mode = self._hvac_mode_dps.get_value(self._device)
  231. return STATE_UNAVAILABLE if hvac_mode is None else hvac_mode
  232. @property
  233. def hvac_modes(self):
  234. """Return available HVAC modes."""
  235. if self._hvac_mode_dps is None:
  236. return []
  237. else:
  238. return self._hvac_mode_dps.values(self._device)
  239. async def async_set_hvac_mode(self, hvac_mode):
  240. """Set new HVAC mode."""
  241. if self._hvac_mode_dps is None:
  242. raise NotImplementedError()
  243. await self._hvac_mode_dps.async_set_value(self._device, hvac_mode)
  244. @property
  245. def preset_mode(self):
  246. """Return the current preset mode."""
  247. if self._preset_mode_dps is None:
  248. raise NotImplementedError()
  249. return self._preset_mode_dps.get_value(self._device)
  250. @property
  251. def preset_modes(self):
  252. """Return the list of presets that this device supports."""
  253. if self._preset_mode_dps is None:
  254. return None
  255. return self._preset_mode_dps.values(self._device)
  256. async def async_set_preset_mode(self, preset_mode):
  257. """Set the preset mode."""
  258. if self._preset_mode_dps is None:
  259. raise NotImplementedError()
  260. await self._preset_mode_dps.async_set_value(self._device, preset_mode)
  261. @property
  262. def swing_mode(self):
  263. """Return the current swing mode."""
  264. if self._swing_mode_dps is None:
  265. raise NotImplementedError()
  266. return self._swing_mode_dps.get_value(self._device)
  267. @property
  268. def swing_modes(self):
  269. """Return the list of swing modes that this device supports."""
  270. if self._swing_mode_dps is None:
  271. return None
  272. return self._swing_mode_dps.values(self._device)
  273. async def async_set_swing_mode(self, swing_mode):
  274. """Set the preset mode."""
  275. if self._swing_mode_dps is None:
  276. raise NotImplementedError()
  277. await self._swing_mode_dps.async_set_value(self._device, swing_mode)
  278. @property
  279. def fan_mode(self):
  280. """Return the current swing mode."""
  281. if self._fan_mode_dps is None:
  282. raise NotImplementedError()
  283. return self._fan_mode_dps.get_value(self._device)
  284. @property
  285. def fan_modes(self):
  286. """Return the list of swing modes that this device supports."""
  287. if self._fan_mode_dps is None:
  288. return None
  289. return self._fan_mode_dps.values(self._device)
  290. async def async_set_fan_mode(self, fan_mode):
  291. """Set the preset mode."""
  292. if self._fan_mode_dps is None:
  293. raise NotImplementedError()
  294. await self._fan_mode_dps.async_set_value(self._device, fan_mode)
  295. @property
  296. def device_state_attributes(self):
  297. """Get additional attributes that the integration itself does not support."""
  298. attr = {}
  299. for a in self._attr_dps:
  300. attr[a.name] = a.get_value(self._device)
  301. return attr
  302. async def async_update(self):
  303. await self._device.async_refresh()