fan.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. """
  2. Platform to control tuya fan devices.
  3. """
  4. import logging
  5. from homeassistant.components.fan import (
  6. FanEntity,
  7. SUPPORT_DIRECTION,
  8. SUPPORT_OSCILLATE,
  9. SUPPORT_PRESET_MODE,
  10. SUPPORT_SET_SPEED,
  11. )
  12. from homeassistant.const import (
  13. STATE_UNAVAILABLE,
  14. )
  15. from ..device import TuyaLocalDevice
  16. from ..helpers.device_config import TuyaEntityConfig
  17. _LOGGER = logging.getLogger(__name__)
  18. class TuyaLocalFan(FanEntity):
  19. """Representation of a Tuya Fan entity."""
  20. def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
  21. """
  22. Initialise the fan device.
  23. Args:
  24. device (TuyaLocalDevice): The device API instance.
  25. config (TuyaEntityConfig): The entity config.
  26. """
  27. self._device = device
  28. self._config = config
  29. self._support_flags = 0
  30. self._switch_dps = None
  31. self._preset_dps = None
  32. self._speed_dps = None
  33. self._oscillate_dps = None
  34. self._direction_dps = None
  35. self._attr_dps = []
  36. for d in config.dps():
  37. if d.name == "switch":
  38. self._switch_dps = d
  39. elif d.name == "preset_mode":
  40. self._preset_dps = d
  41. self._support_flags |= SUPPORT_PRESET_MODE
  42. elif d.name == "speed":
  43. self._speed_dps = d
  44. self._support_flags |= SUPPORT_SET_SPEED
  45. elif d.name == "oscillate":
  46. self._oscillate_dps = d
  47. self._support_flags |= SUPPORT_OSCILLATE
  48. elif d.name == "direction":
  49. self._direction_dps = d
  50. self._support_flags |= SUPPORT_DIRECTION
  51. elif not d.hidden:
  52. self._attr_dps.append(d)
  53. @property
  54. def supported_features(self):
  55. """Return the features supported by this climate device."""
  56. return self._support_flags
  57. @property
  58. def should_poll(self):
  59. """Return the polling state."""
  60. return True
  61. @property
  62. def name(self):
  63. """Return the name of the climate device."""
  64. return self._device.name
  65. @property
  66. def friendly_name(self):
  67. """Return the friendly name of the climate entity for the UI."""
  68. return self._config.name
  69. @property
  70. def unique_id(self):
  71. """Return the unique id for this climate device."""
  72. return self._device.unique_id
  73. @property
  74. def device_info(self):
  75. """Return device information about this heater."""
  76. return self._device.device_info
  77. @property
  78. def icon(self):
  79. """Return the icon to use in the frontend for this device."""
  80. if self.is_on:
  81. return "mdi:fan"
  82. else:
  83. return "mdi:fan-off"
  84. @property
  85. def is_on(self):
  86. """Return whether the switch is on or not."""
  87. # If there is no switch, it is always on
  88. if self._switch_dps is None:
  89. return True
  90. is_switched_on = self._switch_dps.get_value(self._device)
  91. if is_switched_on is None:
  92. return STATE_UNAVAILABLE
  93. else:
  94. return bool(is_switched_on)
  95. async def async_turn_on(self, **kwargs):
  96. """Turn the switch on"""
  97. if self._switch_dps is None:
  98. raise NotImplementedError()
  99. await self._switch_dps.async_set_value(self._device, True)
  100. async def async_turn_off(self, **kwargs):
  101. """Turn the switch off"""
  102. if self._switch_dps is None:
  103. raise NotImplementedError
  104. await self._switch_dps.async_set_value(self._device, False)
  105. @property
  106. def percentage(self):
  107. """Return the currently set percentage."""
  108. if self._speed_dps is None:
  109. return None
  110. return self._speed_dps.get_value(self._device)
  111. @property
  112. def percentage_step(self):
  113. """Return the step for percentage."""
  114. if self._speed_dps is None:
  115. return None
  116. if self._speed_dps.values is None:
  117. return self._speed_dps.step(self._device)
  118. else:
  119. return 100 / len(self._speed_dps.values)
  120. @property
  121. def speed_count(self):
  122. """Return the number of speeds supported by the fan."""
  123. if self._speed_dps is None:
  124. return 0
  125. if self._speed_dps.values is not None:
  126. return len(self._speed_dps.values)
  127. return int(round(100 / self.percentage_step))
  128. async def async_set_percentage(self, percentage):
  129. """Set the fan speed as a percentage."""
  130. if self._speed_dps is None:
  131. return None
  132. # If there is a fixed list of values, snap to the closest one
  133. if self._speed_dps.values is not None:
  134. percentage = min(self._speed_dps.values, key=lambda x: abs(x - percentage))
  135. await self._speed_dps.async_set_value(self._device, percentage)
  136. @property
  137. def preset_mode(self):
  138. """Return the current preset mode."""
  139. if self._preset_dps is None:
  140. return None
  141. return self._preset_dps.get_value(self._device)
  142. @property
  143. def preset_modes(self):
  144. """Return the list of presets that this device supports."""
  145. if self._preset_dps is None:
  146. return []
  147. return self._preset_dps.values
  148. async def async_set_preset_mode(self, preset_mode):
  149. """Set the preset mode."""
  150. if self._preset_dps is None:
  151. raise NotImplementedError()
  152. await self._preset_dps.async_set_value(self._device, preset_mode)
  153. @property
  154. def current_direction(self):
  155. """Return the current direction [forward or reverse]."""
  156. if self._direction_dps is None:
  157. return None
  158. return self._direction_dps.get_value(self._device)
  159. async def async_set_direction(self, direction):
  160. """Set the direction of the fan."""
  161. if self._direction_dps is None:
  162. raise NotImplementedError()
  163. await self._direction_dps.async_set_value(self._device, direction)
  164. @property
  165. def oscillating(self):
  166. """Return whether or not the fan is oscillating."""
  167. if self._oscillate_dps is None:
  168. return None
  169. return self._oscillate_dps.get_value(self._device)
  170. async def async_oscillate(self, oscillating):
  171. """Oscillate the fan."""
  172. if self._oscillate_dps is None:
  173. raise NotImplementedError()
  174. await self._oscillate_dps.async_set_value(self._device, oscillating)
  175. @property
  176. def device_state_attributes(self):
  177. """Get additional attributes that the integration itself does not support."""
  178. attr = {}
  179. for a in self._attr_dps:
  180. attr[a.name] = a.get_value(self._device)
  181. return attr
  182. async def async_update(self):
  183. await self._device.async_refresh()