water_heater.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. WaterHeaterEntity,
  8. WaterHeaterEntityFeature,
  9. )
  10. from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
  11. import logging
  12. from .device import TuyaLocalDevice
  13. from .helpers.config import async_tuya_setup_platform
  14. from .helpers.device_config import TuyaEntityConfig
  15. from .helpers.mixin import TuyaLocalEntity, unit_from_ascii
  16. _LOGGER = logging.getLogger(__name__)
  17. async def async_setup_entry(hass, config_entry, async_add_entities):
  18. config = {**config_entry.data, **config_entry.options}
  19. await async_tuya_setup_platform(
  20. hass,
  21. async_add_entities,
  22. config,
  23. "water_heater",
  24. TuyaLocalWaterHeater,
  25. )
  26. def validate_temp_unit(unit):
  27. unit = unit_from_ascii(unit)
  28. try:
  29. return UnitOfTemperature(unit)
  30. except ValueError:
  31. return None
  32. class TuyaLocalWaterHeater(TuyaLocalEntity, WaterHeaterEntity):
  33. """Representation of a Tuya water heater entity."""
  34. def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
  35. """
  36. Initialise the water heater device.
  37. Args:
  38. device (TuyaLocalDevice): The device API instance.
  39. config (TuyaEntityConfig): The entity config.
  40. """
  41. dps_map = self._init_begin(device, config)
  42. self._current_temperature_dps = dps_map.pop(ATTR_CURRENT_TEMPERATURE, None)
  43. self._temperature_dps = dps_map.pop(ATTR_TEMPERATURE, None)
  44. self._unit_dps = dps_map.pop("temperature_unit", None)
  45. self._mintemp_dps = dps_map.pop("min_temperature", None)
  46. self._maxtemp_dps = dps_map.pop("max_temperature", None)
  47. self._operation_mode_dps = dps_map.pop("operation_mode", None)
  48. self._init_end(dps_map)
  49. self._support_flags = 0
  50. if self._operation_mode_dps:
  51. self._support_flags |= WaterHeaterEntityFeature.OPERATION_MODE
  52. if self._temperature_dps and not self._temperature_dps.readonly:
  53. self._support_flags |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
  54. @property
  55. def supported_features(self):
  56. """Return the features supported by this climate device."""
  57. return self._support_flags
  58. @property
  59. def temperature_unit(self):
  60. """Return the unit of measurement."""
  61. # If there is a separate DPS that returns the units, use that
  62. if self._unit_dps is not None:
  63. unit = validate_temp_unit(self._unit_dps.get_value(self._device))
  64. # Only return valid units
  65. if unit is not None:
  66. return unit
  67. # If there unit attribute configured in the temperature dps, use that
  68. if self._temperature_dps:
  69. unit = validate_temp_unit(self._temperature_dps.unit)
  70. if unit is not None:
  71. return unit
  72. # Return the default unit from the device
  73. return UnitOfTemperature.CELSIUS
  74. @property
  75. def precision(self):
  76. """Return the precision of the temperature setting."""
  77. # unlike sensor, this is a decimal of the smallest unit that can be
  78. # represented, not a number of decimal places.
  79. return 1.0 / max(
  80. self._temperature_dps.scale(self._device),
  81. self._current_temperature_dps.scale(self._device),
  82. )
  83. @property
  84. def current_operation(self):
  85. """Return current operation ie. eco, electric, performance, ..."""
  86. return self._operation_mode_dps.get_value(self._device)
  87. @property
  88. def operation_list(self):
  89. """Return the list of available operation modes."""
  90. if self._operation_mode_dps is None:
  91. return []
  92. else:
  93. return self._operation_mode_dps.values(self._device)
  94. @property
  95. def current_temperature(self):
  96. """Return the current temperature."""
  97. if self._current_temperature_dps is None:
  98. return None
  99. return self._current_temperature_dps.get_value(self._device)
  100. @property
  101. def target_temperature(self):
  102. """Return the temperature we try to reach."""
  103. if self._temperature_dps is None:
  104. raise NotImplementedError()
  105. return self._temperature_dps.get_value(self._device)
  106. @property
  107. def target_temperature_step(self):
  108. """Return the supported step of target temperature."""
  109. dps = self._temperature_dps
  110. if dps is None:
  111. return 1
  112. return dps.step(self._device)
  113. async def async_set_temperature(self, **kwargs):
  114. """Set the target temperature of the water heater."""
  115. if kwargs.get(ATTR_OPERATION_MODE) is not None:
  116. if self._operation_mode_dps is None:
  117. raise NotImplementedError()
  118. await self.async_set_operation_mode(kwargs.get(ATTR_OPERATION_MODE))
  119. if kwargs.get(ATTR_TEMPERATURE) is not None:
  120. if self._temperature_dps is None:
  121. raise NotImplementedError()
  122. await self._temperature_dps.async_set_value(
  123. self._device, kwargs.get(ATTR_TEMPERATURE)
  124. )
  125. async def async_set_operation_mode(self, operation_mode):
  126. """Set new target operation mode."""
  127. if self._operation_mode_dps is None:
  128. raise NotImplementedError()
  129. await self._operation_mode_dps.async_set_value(self._device, operation_mode)
  130. @property
  131. def min_temp(self):
  132. """Return the minimum supported target temperature."""
  133. # if a separate min_temperature dps is specified, the device tells us.
  134. if self._mintemp_dps is not None:
  135. return self._mintemp_dps.get_value(self._device)
  136. if self._temperature_dps:
  137. r = self._temperature_dps.range(self._device)
  138. return r.get("min")
  139. @property
  140. def max_temp(self):
  141. """Return the maximum supported target temperature."""
  142. # if a separate max_temperature dps is specified, the device tells us.
  143. if self._maxtemp_dps is not None:
  144. return self._maxtemp_dps.get_value(self._device)
  145. if self._temperature_dps:
  146. r = self._temperature_dps.range(self._device)
  147. return r.get("max")