climate.py 13 KB

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