vacuum.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. """Support for Gardena mower."""
  2. import asyncio
  3. import logging
  4. from datetime import timedelta
  5. from homeassistant.core import callback
  6. from homeassistant.components.vacuum import (
  7. StateVacuumEntity,
  8. SUPPORT_BATTERY,
  9. SUPPORT_RETURN_HOME,
  10. SUPPORT_STATE,
  11. SUPPORT_STOP,
  12. SUPPORT_START,
  13. STATE_PAUSED,
  14. STATE_CLEANING,
  15. STATE_DOCKED,
  16. STATE_RETURNING,
  17. STATE_ERROR,
  18. ATTR_BATTERY_LEVEL,
  19. )
  20. from .const import (
  21. ATTR_ACTIVITY,
  22. ATTR_BATTERY_STATE,
  23. ATTR_NAME,
  24. ATTR_OPERATING_HOURS,
  25. ATTR_RF_LINK_LEVEL,
  26. ATTR_RF_LINK_STATE,
  27. ATTR_SERIAL,
  28. ATTR_LAST_ERROR,
  29. ATTR_ERROR,
  30. ATTR_STATE,
  31. CONF_MOWER_DURATION,
  32. DEFAULT_MOWER_DURATION,
  33. DOMAIN,
  34. GARDENA_LOCATION,
  35. )
  36. from .sensor import GardenaSensor
  37. _LOGGER = logging.getLogger(__name__)
  38. SCAN_INTERVAL = timedelta(minutes=1)
  39. SUPPORT_GARDENA = (
  40. SUPPORT_BATTERY | SUPPORT_RETURN_HOME | SUPPORT_STOP | SUPPORT_START | SUPPORT_STATE
  41. )
  42. async def async_setup_entry(hass, config_entry, async_add_entities):
  43. """Set up the Gardena smart mower system."""
  44. entities = []
  45. for mower in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("MOWER"):
  46. entities.append(GardenaSmartMower(hass, mower, config_entry.options))
  47. _LOGGER.debug("Adding mower as vacuums: %s", entities)
  48. async_add_entities(entities, True)
  49. class GardenaSmartMower(StateVacuumEntity):
  50. """Representation of a Gardena Connected Mower."""
  51. def __init__(self, hass, mower, options):
  52. """Initialize the Gardena Connected Mower."""
  53. self.hass = hass
  54. self._device = mower
  55. self._options = options
  56. self._name = "{}".format(self._device.name)
  57. self._unique_id = f"{self._device.serial}-mower"
  58. self._state = None
  59. self._error_message = ""
  60. async def async_added_to_hass(self):
  61. """Subscribe to events."""
  62. self._device.add_callback(self.update_callback)
  63. @property
  64. def should_poll(self) -> bool:
  65. """No polling needed for a vacuum."""
  66. return False
  67. def update_callback(self, device):
  68. """Call update for Home Assistant when the device is updated."""
  69. self.schedule_update_ha_state(True)
  70. async def async_update(self):
  71. """Update the states of Gardena devices."""
  72. _LOGGER.debug("Running Gardena update")
  73. # Managing state
  74. state = self._device.state
  75. _LOGGER.debug("Mower has state %s", state)
  76. if state in ["WARNING", "ERROR", "UNAVAILABLE"]:
  77. _LOGGER.debug("Mower has an error")
  78. self._state = STATE_ERROR
  79. self._error_message = self._device.last_error_code
  80. else:
  81. _LOGGER.debug("Getting mower state")
  82. activity = self._device.activity
  83. _LOGGER.debug("Mower has activity %s", activity)
  84. if activity == "PAUSED":
  85. self._state = STATE_PAUSED
  86. elif activity in [
  87. "OK_CUTTING",
  88. "OK_CUTTING_TIMER_OVERRIDDEN",
  89. "OK_LEAVING",
  90. ]:
  91. self._state = STATE_CLEANING
  92. elif activity == "OK_SEARCHING":
  93. self._state = STATE_RETURNING
  94. elif activity in [
  95. "OK_CHARGING",
  96. "PARKED_TIMER",
  97. "PARKED_PARK_SELECTED",
  98. "PARKED_AUTOTIMER",
  99. ]:
  100. self._state = STATE_DOCKED
  101. elif activity == "NONE":
  102. self._state = None
  103. _LOGGER.debug("Mower has no activity")
  104. @property
  105. def name(self):
  106. """Return the name of the device."""
  107. return self._device.name
  108. @property
  109. def supported_features(self):
  110. """Flag lawn mower robot features that are supported."""
  111. return SUPPORT_GARDENA
  112. @property
  113. def battery_level(self):
  114. """Return the battery level of the lawn mower."""
  115. return self._device.battery_level
  116. @property
  117. def state(self):
  118. """Return the status of the lawn mower."""
  119. return self._state
  120. @property
  121. def available(self):
  122. """Return True if the device is available."""
  123. return self._device.state != "UNAVAILABLE"
  124. def error(self):
  125. """Return the error message."""
  126. if self._state == STATE_ERROR:
  127. return self._error_message
  128. return ""
  129. @property
  130. def extra_state_attributes(self):
  131. """Return the state attributes of the lawn mower."""
  132. return {
  133. ATTR_ACTIVITY: self._device.activity,
  134. ATTR_BATTERY_LEVEL: self._device.battery_level,
  135. ATTR_BATTERY_STATE: self._device.battery_state,
  136. ATTR_RF_LINK_LEVEL: self._device.rf_link_level,
  137. ATTR_RF_LINK_STATE: self._device.rf_link_state,
  138. ATTR_OPERATING_HOURS: self._device.operating_hours,
  139. ATTR_LAST_ERROR: self._device.last_error_code,
  140. ATTR_ERROR: "NONE" if self._device.activity != "NONE" else self._device.last_error_code,
  141. ATTR_STATE: self._device.activity if self._device.activity != "NONE" else self._device.last_error_code
  142. }
  143. @property
  144. def option_mower_duration(self) -> int:
  145. return self._options.get(CONF_MOWER_DURATION, DEFAULT_MOWER_DURATION)
  146. def start(self):
  147. """Start the mower using Gardena API command START_SECONDS_TO_OVERRIDE. Duration is read from integration options."""
  148. duration = self.option_mower_duration * 60
  149. _LOGGER.debug("Mower command: vacuum.start => START_SECONDS_TO_OVERRIDE, %s", duration)
  150. return asyncio.run_coroutine_threadsafe(
  151. self._device.start_seconds_to_override(duration), self.hass.loop
  152. ).result()
  153. def stop(self, **kwargs):
  154. """Stop the mower using Gardena API command PARK_UNTIL_FURTHER_NOTICE."""
  155. _LOGGER.debug("Mower command: vacuum.stop => PARK_UNTIL_FURTHER_NOTICE")
  156. asyncio.run_coroutine_threadsafe(
  157. self._device.park_until_further_notice(), self.hass.loop
  158. ).result()
  159. def turn_on(self, **kwargs):
  160. """Start the mower using Gardena API command START_DONT_OVERRIDE."""
  161. _LOGGER.debug("Mower command: vacuum.turn_on => START_DONT_OVERRIDE")
  162. asyncio.run_coroutine_threadsafe(
  163. self._device.start_dont_override(), self.hass.loop
  164. ).result()
  165. def turn_off(self, **kwargs):
  166. """Stop the mower using Gardena API command PARK_UNTIL_FURTHER_NOTICE."""
  167. _LOGGER.debug("Mower command: vacuum.turn_off => PARK_UNTIL_FURTHER_NOTICE")
  168. asyncio.run_coroutine_threadsafe(
  169. self._device.park_until_further_notice(), self.hass.loop
  170. ).result()
  171. def return_to_base(self, **kwargs):
  172. """Stop the mower using Gardena API command PARK_UNTIL_NEXT_TASK."""
  173. _LOGGER.debug("Mower command: vacuum.return_to_base => PARK_UNTIL_NEXT_TASK")
  174. asyncio.run_coroutine_threadsafe(
  175. self._device.park_until_next_task(), self.hass.loop
  176. ).result()
  177. @property
  178. def unique_id(self) -> str:
  179. """Return a unique ID."""
  180. return self._unique_id
  181. @property
  182. def device_info(self):
  183. return {
  184. "identifiers": {
  185. # Serial numbers are unique identifiers within a specific domain
  186. (DOMAIN, self._device.serial)
  187. },
  188. "name": self._device.name,
  189. "manufacturer": "Gardena",
  190. "model": self._device.model_type,
  191. }