switch.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. """Support for Gardena switch (Power control, water control, smart irrigation control)."""
  2. import asyncio
  3. import logging
  4. from homeassistant.core import callback
  5. from homeassistant.components.switch import SwitchEntity
  6. from homeassistant.const import ATTR_BATTERY_LEVEL
  7. from .const import (
  8. ATTR_ACTIVITY,
  9. ATTR_BATTERY_STATE,
  10. ATTR_LAST_ERROR,
  11. ATTR_RF_LINK_LEVEL,
  12. ATTR_RF_LINK_STATE,
  13. ATTR_SERIAL,
  14. CONF_SMART_IRRIGATION_DURATION,
  15. CONF_SMART_WATERING_DURATION,
  16. DEFAULT_SMART_IRRIGATION_DURATION,
  17. DEFAULT_SMART_WATERING_DURATION,
  18. DOMAIN,
  19. GARDENA_LOCATION,
  20. )
  21. from .sensor import GardenaSensor
  22. _LOGGER = logging.getLogger(__name__)
  23. async def async_setup_entry(hass, config_entry, async_add_entities):
  24. """Set up the switches platform."""
  25. entities = []
  26. for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("WATER_CONTROL"):
  27. entities.append(GardenaSmartWaterControl(water_control, config_entry.options))
  28. for power_switch in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("POWER_SOCKET"):
  29. entities.append(GardenaPowerSocket(power_switch))
  30. for smart_irrigation in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SMART_IRRIGATION_CONTROL"):
  31. for valve in smart_irrigation.valves.values():
  32. entities.append(GardenaSmartIrrigationControl(
  33. smart_irrigation, valve['id'], config_entry.options))
  34. _LOGGER.debug(
  35. "Adding water control, power socket and smart irrigation control as switch: %s",
  36. entities)
  37. async_add_entities(entities, True)
  38. class GardenaSmartWaterControl(SwitchEntity):
  39. """Representation of a Gardena Smart Water Control."""
  40. def __init__(self, wc, options):
  41. """Initialize the Gardena Smart Water Control."""
  42. self._device = wc
  43. self._options = options
  44. self._name = f"{self._device.name}"
  45. self._unique_id = f"{self._device.serial}-valve"
  46. self._state = None
  47. self._error_message = ""
  48. async def async_added_to_hass(self):
  49. """Subscribe to events."""
  50. self._device.add_callback(self.update_callback)
  51. @property
  52. def should_poll(self) -> bool:
  53. """No polling needed for a water valve."""
  54. return False
  55. def update_callback(self, device):
  56. """Call update for Home Assistant when the device is updated."""
  57. self.schedule_update_ha_state(True)
  58. async def async_update(self):
  59. """Update the states of Gardena devices."""
  60. _LOGGER.debug("Running Gardena update")
  61. # Managing state
  62. state = self._device.valve_state
  63. _LOGGER.debug("Water control has state %s", state)
  64. if state in ["WARNING", "ERROR", "UNAVAILABLE"]:
  65. _LOGGER.debug("Water control has an error")
  66. self._state = False
  67. self._error_message = self._device.last_error_code
  68. else:
  69. _LOGGER.debug("Getting water control state")
  70. activity = self._device.valve_activity
  71. self._error_message = ""
  72. _LOGGER.debug("Water control has activity %s", activity)
  73. if activity == "CLOSED":
  74. self._state = False
  75. elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]:
  76. self._state = True
  77. else:
  78. _LOGGER.debug("Water control has none activity")
  79. @property
  80. def name(self):
  81. """Return the name of the device."""
  82. return self._name
  83. @property
  84. def unique_id(self) -> str:
  85. """Return a unique ID."""
  86. return self._unique_id
  87. @property
  88. def is_on(self):
  89. """Return true if it is on."""
  90. return self._state
  91. @property
  92. def available(self):
  93. """Return True if the device is available."""
  94. return self._device.valve_state != "UNAVAILABLE"
  95. def error(self):
  96. """Return the error message."""
  97. return self._error_message
  98. @property
  99. def extra_state_attributes(self):
  100. """Return the state attributes of the water valve."""
  101. return {
  102. ATTR_ACTIVITY: self._device.valve_activity,
  103. ATTR_BATTERY_LEVEL: self._device.battery_level,
  104. ATTR_BATTERY_STATE: self._device.battery_state,
  105. ATTR_RF_LINK_LEVEL: self._device.rf_link_level,
  106. ATTR_RF_LINK_STATE: self._device.rf_link_state,
  107. ATTR_LAST_ERROR: self._error_message,
  108. }
  109. @property
  110. def option_smart_watering_duration(self) -> int:
  111. return self._options.get(
  112. CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION
  113. )
  114. def turn_on(self, **kwargs):
  115. """Start watering."""
  116. duration = self.option_smart_watering_duration * 60
  117. return asyncio.run_coroutine_threadsafe(
  118. self._device.start_seconds_to_override(duration), self.hass.loop
  119. ).result()
  120. def turn_off(self, **kwargs):
  121. """Stop watering."""
  122. return asyncio.run_coroutine_threadsafe(
  123. self._device.stop_until_next_task(), self.hass.loop
  124. ).result()
  125. @property
  126. def device_info(self):
  127. return {
  128. "identifiers": {
  129. # Serial numbers are unique identifiers within a specific domain
  130. (DOMAIN, self._device.serial)
  131. },
  132. "name": self._device.name,
  133. "manufacturer": "Gardena",
  134. "model": self._device.model_type,
  135. }
  136. class GardenaPowerSocket(SwitchEntity):
  137. """Representation of a Gardena Power Socket."""
  138. def __init__(self, ps):
  139. """Initialize the Gardena Power Socket."""
  140. self._device = ps
  141. self._name = f"{self._device.name}"
  142. self._unique_id = f"{self._device.serial}"
  143. self._state = None
  144. self._error_message = ""
  145. async def async_added_to_hass(self):
  146. """Subscribe to events."""
  147. self._device.add_callback(self.update_callback)
  148. @property
  149. def should_poll(self) -> bool:
  150. """No polling needed for a power socket."""
  151. return False
  152. def update_callback(self, device):
  153. """Call update for Home Assistant when the device is updated."""
  154. self.schedule_update_ha_state(True)
  155. async def async_update(self):
  156. """Update the states of Gardena devices."""
  157. _LOGGER.debug("Running Gardena update")
  158. # Managing state
  159. state = self._device.state
  160. _LOGGER.debug("Power socket has state %s", state)
  161. if state in ["WARNING", "ERROR", "UNAVAILABLE"]:
  162. _LOGGER.debug("Power socket has an error")
  163. self._state = False
  164. self._error_message = self._device.last_error_code
  165. else:
  166. _LOGGER.debug("Getting Power socket state")
  167. activity = self._device.activity
  168. self._error_message = ""
  169. _LOGGER.debug("Power socket has activity %s", activity)
  170. if activity == "OFF":
  171. self._state = False
  172. elif activity in ["FOREVER_ON", "TIME_LIMITED_ON", "SCHEDULED_ON"]:
  173. self._state = True
  174. else:
  175. _LOGGER.debug("Power socket has none activity")
  176. @property
  177. def name(self):
  178. """Return the name of the device."""
  179. return self._name
  180. @property
  181. def unique_id(self) -> str:
  182. """Return a unique ID."""
  183. return self._unique_id
  184. @property
  185. def is_on(self):
  186. """Return true if it is on."""
  187. return self._state
  188. @property
  189. def available(self):
  190. """Return True if the device is available."""
  191. return self._device.state != "UNAVAILABLE"
  192. def error(self):
  193. """Return the error message."""
  194. return self._error_message
  195. @property
  196. def extra_state_attributes(self):
  197. """Return the state attributes of the power switch."""
  198. return {
  199. ATTR_ACTIVITY: self._device.activity,
  200. ATTR_RF_LINK_LEVEL: self._device.rf_link_level,
  201. ATTR_RF_LINK_STATE: self._device.rf_link_state,
  202. ATTR_LAST_ERROR: self._error_message,
  203. }
  204. def turn_on(self, **kwargs):
  205. """Start watering."""
  206. return asyncio.run_coroutine_threadsafe(
  207. self._device.start_override(), self.hass.loop
  208. ).result()
  209. def turn_off(self, **kwargs):
  210. """Stop watering."""
  211. return asyncio.run_coroutine_threadsafe(
  212. self._device.stop_until_next_task(), self.hass.loop
  213. ).result()
  214. @property
  215. def device_info(self):
  216. return {
  217. "identifiers": {
  218. # Serial numbers are unique identifiers within a specific domain
  219. (DOMAIN, self._device.serial)
  220. },
  221. "name": self._device.name,
  222. "manufacturer": "Gardena",
  223. "model": self._device.model_type,
  224. }
  225. class GardenaSmartIrrigationControl(SwitchEntity):
  226. """Representation of a Gardena Smart Irrigation Control."""
  227. def __init__(self, sic, valve_id, options):
  228. """Initialize the Gardena Smart Irrigation Control."""
  229. self._device = sic
  230. self._valve_id = valve_id
  231. self._options = options
  232. self._name = f"{self._device.name} - {self._device.valves[self._valve_id]['name']}"
  233. self._unique_id = f"{self._device.serial}-{self._valve_id}"
  234. self._state = None
  235. self._error_message = ""
  236. async def async_added_to_hass(self):
  237. """Subscribe to events."""
  238. self._device.add_callback(self.update_callback)
  239. @property
  240. def should_poll(self) -> bool:
  241. """No polling needed for a smart irrigation control."""
  242. return False
  243. def update_callback(self, device):
  244. """Call update for Home Assistant when the device is updated."""
  245. self.schedule_update_ha_state(True)
  246. async def async_update(self):
  247. """Update the states of Gardena devices."""
  248. _LOGGER.debug("Running Gardena update")
  249. # Managing state
  250. valve = self._device.valves[self._valve_id]
  251. _LOGGER.debug("Valve has state: %s", valve["state"])
  252. if valve["state"] in ["WARNING", "ERROR", "UNAVAILABLE"]:
  253. _LOGGER.debug("Valve has an error")
  254. self._state = False
  255. self._error_message = valve["last_error_code"]
  256. else:
  257. _LOGGER.debug("Getting Valve state")
  258. activity = valve["activity"]
  259. self._error_message = ""
  260. _LOGGER.debug("Valve has activity: %s", activity)
  261. if activity == "CLOSED":
  262. self._state = False
  263. elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]:
  264. self._state = True
  265. else:
  266. _LOGGER.debug("Valve has unknown activity")
  267. @property
  268. def name(self):
  269. """Return the name of the device."""
  270. return self._name
  271. @property
  272. def unique_id(self) -> str:
  273. """Return a unique ID."""
  274. return self._unique_id
  275. @property
  276. def is_on(self):
  277. """Return true if it is on."""
  278. return self._state
  279. @property
  280. def available(self):
  281. """Return True if the device is available."""
  282. return self._device.valves[self._valve_id]["state"] != "UNAVAILABLE"
  283. def error(self):
  284. """Return the error message."""
  285. return self._error_message
  286. @property
  287. def extra_state_attributes(self):
  288. """Return the state attributes of the smart irrigation control."""
  289. return {
  290. ATTR_ACTIVITY: self._device.valves[self._valve_id]["activity"],
  291. ATTR_RF_LINK_LEVEL: self._device.rf_link_level,
  292. ATTR_RF_LINK_STATE: self._device.rf_link_state,
  293. ATTR_LAST_ERROR: self._error_message,
  294. }
  295. @property
  296. def option_smart_irrigation_duration(self) -> int:
  297. return self._options.get(
  298. CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION
  299. )
  300. def turn_on(self, **kwargs):
  301. """Start watering."""
  302. duration = self.option_smart_irrigation_duration * 60
  303. return asyncio.run_coroutine_threadsafe(
  304. self._device.start_seconds_to_override(duration, self._valve_id), self.hass.loop
  305. ).result()
  306. def turn_off(self, **kwargs):
  307. """Stop watering."""
  308. return asyncio.run_coroutine_threadsafe(
  309. self._device.stop_until_next_task(self._valve_id), self.hass.loop
  310. ).result()
  311. @property
  312. def device_info(self):
  313. return {
  314. "identifiers": {
  315. # Serial numbers are unique identifiers within a specific domain
  316. (DOMAIN, self._device.serial)
  317. },
  318. "name": self._device.name,
  319. "manufacturer": "Gardena",
  320. "model": self._device.model_type,
  321. }