water_heater.py 6.0 KB

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