water_heater.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. """
  2. Setup for different kinds of Tuya water heater devices
  3. """
  4. from homeassistant.components.water_heater import (
  5. ATTR_CURRENT_TEMPERATURE,
  6. ATTR_OPERATION_MODE,
  7. ATTR_AWAY_MODE,
  8. WaterHeaterEntity,
  9. WaterHeaterEntityFeature,
  10. )
  11. from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
  12. import logging
  13. from .device import TuyaLocalDevice
  14. from .helpers.config import async_tuya_setup_platform
  15. from .helpers.device_config import TuyaEntityConfig
  16. from .helpers.mixin import TuyaLocalEntity, unit_from_ascii
  17. _LOGGER = logging.getLogger(__name__)
  18. async def async_setup_entry(hass, config_entry, async_add_entities):
  19. config = {**config_entry.data, **config_entry.options}
  20. await async_tuya_setup_platform(
  21. hass,
  22. async_add_entities,
  23. config,
  24. "water_heater",
  25. TuyaLocalWaterHeater,
  26. )
  27. def validate_temp_unit(unit):
  28. unit = unit_from_ascii(unit)
  29. try:
  30. return UnitOfTemperature(unit)
  31. except ValueError:
  32. return None
  33. class TuyaLocalWaterHeater(TuyaLocalEntity, WaterHeaterEntity):
  34. """Representation of a Tuya water heater entity."""
  35. def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
  36. """
  37. Initialise the water heater device.
  38. Args:
  39. device (TuyaLocalDevice): The device API instance.
  40. config (TuyaEntityConfig): The entity config.
  41. """
  42. dps_map = self._init_begin(device, config)
  43. self._current_temperature_dps = dps_map.pop(ATTR_CURRENT_TEMPERATURE, None)
  44. self._temperature_dps = dps_map.pop(ATTR_TEMPERATURE, None)
  45. self._unit_dps = dps_map.pop("temperature_unit", None)
  46. self._mintemp_dps = dps_map.pop("min_temperature", None)
  47. self._maxtemp_dps = dps_map.pop("max_temperature", None)
  48. self._operation_mode_dps = dps_map.pop(ATTR_OPERATION_MODE, None)
  49. self._away_mode_dps = dps_map.pop(ATTR_AWAY_MODE, None)
  50. self._init_end(dps_map)
  51. self._support_flags = 0
  52. if self._operation_mode_dps:
  53. self._support_flags |= WaterHeaterEntityFeature.OPERATION_MODE
  54. if self._temperature_dps and not self._temperature_dps.readonly:
  55. self._support_flags |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
  56. if self._away_mode_dps or (
  57. self._operation_mode_dps
  58. and "away" in self._operation_mode_dps.values(device)
  59. ):
  60. self._support_flags |= WaterHeaterEntityFeature.AWAY_MODE
  61. @property
  62. def supported_features(self):
  63. """Return the features supported by this climate device."""
  64. return self._support_flags
  65. @property
  66. def temperature_unit(self):
  67. """Return the unit of measurement."""
  68. # If there is a separate DPS that returns the units, use that
  69. if self._unit_dps is not None:
  70. unit = validate_temp_unit(self._unit_dps.get_value(self._device))
  71. # Only return valid units
  72. if unit is not None:
  73. return unit
  74. # If there unit attribute configured in the temperature dps, use that
  75. if self._temperature_dps:
  76. unit = validate_temp_unit(self._temperature_dps.unit)
  77. if unit is not None:
  78. return unit
  79. # Return the default unit from the device
  80. return UnitOfTemperature.CELSIUS
  81. @property
  82. def precision(self):
  83. """Return the precision of the temperature setting."""
  84. # unlike sensor, this is a decimal of the smallest unit that can be
  85. # represented, not a number of decimal places.
  86. return 1.0 / max(
  87. self._temperature_dps.scale(self._device),
  88. self._current_temperature_dps.scale(self._device),
  89. )
  90. @property
  91. def current_operation(self):
  92. """Return current operation ie. eco, electric, performance, ..."""
  93. return self._operation_mode_dps.get_value(self._device)
  94. @property
  95. def operation_list(self):
  96. """Return the list of available operation modes."""
  97. if self._operation_mode_dps is None:
  98. return []
  99. else:
  100. return self._operation_mode_dps.values(self._device)
  101. @property
  102. def is_away_mode_on(self):
  103. if self._away_mode_dps:
  104. return self._away_mode_dps.get_value(self._device)
  105. elif self._operation_mode_dps and "away" in self._operation_mode_dps.values(
  106. self._device
  107. ):
  108. return self.current_operation == "away"
  109. @property
  110. def current_temperature(self):
  111. """Return the current temperature."""
  112. if self._current_temperature_dps is None:
  113. return None
  114. return self._current_temperature_dps.get_value(self._device)
  115. @property
  116. def target_temperature(self):
  117. """Return the temperature we try to reach."""
  118. if self._temperature_dps is None:
  119. raise NotImplementedError()
  120. return self._temperature_dps.get_value(self._device)
  121. @property
  122. def target_temperature_step(self):
  123. """Return the supported step of target temperature."""
  124. dps = self._temperature_dps
  125. if dps is None:
  126. return 1
  127. return dps.step(self._device)
  128. async def async_set_temperature(self, **kwargs):
  129. """Set the target temperature of the water heater."""
  130. if kwargs.get(ATTR_OPERATION_MODE) is not None:
  131. if self._operation_mode_dps is None:
  132. raise NotImplementedError()
  133. await self.async_set_operation_mode(kwargs.get(ATTR_OPERATION_MODE))
  134. if kwargs.get(ATTR_TEMPERATURE) is not None:
  135. if self._temperature_dps is None:
  136. raise NotImplementedError()
  137. await self._temperature_dps.async_set_value(
  138. self._device, kwargs.get(ATTR_TEMPERATURE)
  139. )
  140. async def async_set_operation_mode(self, operation_mode):
  141. """Set new target operation mode."""
  142. if self._operation_mode_dps is None:
  143. raise NotImplementedError()
  144. await self._operation_mode_dps.async_set_value(self._device, operation_mode)
  145. async def async_turn_away_mode_on(self):
  146. """Turn away mode on"""
  147. if self._away_mode_dps:
  148. await self._away_mode_dps.async_set_value(self._device, True)
  149. elif self._operation_mode_dps and "away" in self._operation_mode_dps.values(
  150. self._device
  151. ):
  152. await self.async_set_operation_mode("away")
  153. else:
  154. raise NotImplementedError()
  155. async def async_turn_away_mode_off(self):
  156. """Turn away mode off"""
  157. if self._away_mode_dps:
  158. await self._away_mode_dps.async_set_value(self._device, False)
  159. elif self._operation_mode_dps and "away" in self._operation_mode_dps.values(
  160. self._device
  161. ):
  162. # switch to the default mode
  163. await self.async_set_operation_mode(self._operation_mode_dps.default)
  164. else:
  165. raise NotImplementedError()
  166. @property
  167. def min_temp(self):
  168. """Return the minimum supported target temperature."""
  169. # if a separate min_temperature dps is specified, the device tells us.
  170. if self._mintemp_dps is not None:
  171. return self._mintemp_dps.get_value(self._device)
  172. if self._temperature_dps:
  173. r = self._temperature_dps.range(self._device)
  174. return r.get("min")
  175. @property
  176. def max_temp(self):
  177. """Return the maximum supported target temperature."""
  178. # if a separate max_temperature dps is specified, the device tells us.
  179. if self._maxtemp_dps is not None:
  180. return self._maxtemp_dps.get_value(self._device)
  181. if self._temperature_dps:
  182. r = self._temperature_dps.range(self._device)
  183. return r.get("max")
  184. async def async_turn_on(self):
  185. """
  186. Turn on the water heater. Works only if operation_mode is a
  187. boolean dp.
  188. """
  189. if self._operation_mode_dps and self._operation_mode_dps.type is bool:
  190. await self._device.async_set_property(
  191. self._operation_mode_dps.id,
  192. True,
  193. )
  194. async def async_turn_off(self):
  195. """
  196. Turn off the water heater. Works only if operation_mode is a
  197. boolean dp.
  198. """
  199. if self._operation_mode_dps and self._operation_mode_dps.type is bool:
  200. await self._device.async_set_property(
  201. self._operation_mode_dps.id,
  202. False,
  203. )