"""Support for Gardena switch (Power control, water control, smart irrigation control).""" import asyncio import logging from homeassistant.core import callback from homeassistant.components.switch import SwitchEntity from homeassistant.const import ATTR_BATTERY_LEVEL from .const import ( ATTR_ACTIVITY, ATTR_BATTERY_STATE, ATTR_LAST_ERROR, ATTR_RF_LINK_LEVEL, ATTR_RF_LINK_STATE, ATTR_SERIAL, CONF_SMART_IRRIGATION_DURATION, CONF_SMART_WATERING_DURATION, DEFAULT_SMART_IRRIGATION_DURATION, DEFAULT_SMART_WATERING_DURATION, DOMAIN, GARDENA_LOCATION, ) from .sensor import GardenaSensor _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the switches platform.""" entities = [] for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("WATER_CONTROL"): entities.append(GardenaSmartWaterControl(water_control, config_entry.options)) for power_switch in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("POWER_SOCKET"): entities.append(GardenaPowerSocket(power_switch)) for smart_irrigation in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SMART_IRRIGATION_CONTROL"): for valve in smart_irrigation.valves.values(): entities.append(GardenaSmartIrrigationControl( smart_irrigation, valve['id'], config_entry.options)) _LOGGER.debug( "Adding water control, power socket and smart irrigation control as switch: %s", entities) async_add_entities(entities, True) class GardenaSmartWaterControl(SwitchEntity): """Representation of a Gardena Smart Water Control.""" def __init__(self, wc, options): """Initialize the Gardena Smart Water Control.""" self._device = wc self._options = options self._name = f"{self._device.name}" self._unique_id = f"{self._device.serial}-valve" self._state = None self._error_message = "" async def async_added_to_hass(self): """Subscribe to events.""" self._device.add_callback(self.update_callback) @property def should_poll(self) -> bool: """No polling needed for a water valve.""" return False def update_callback(self, device): """Call update for Home Assistant when the device is updated.""" self.schedule_update_ha_state(True) async def async_update(self): """Update the states of Gardena devices.""" _LOGGER.debug("Running Gardena update") # Managing state state = self._device.valve_state _LOGGER.debug("Water control has state %s", state) if state in ["WARNING", "ERROR", "UNAVAILABLE"]: _LOGGER.debug("Water control has an error") self._state = False self._error_message = self._device.last_error_code else: _LOGGER.debug("Getting water control state") activity = self._device.valve_activity self._error_message = "" _LOGGER.debug("Water control has activity %s", activity) if activity == "CLOSED": self._state = False elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: self._state = True else: _LOGGER.debug("Water control has none activity") @property def name(self): """Return the name of the device.""" return self._name @property def unique_id(self) -> str: """Return a unique ID.""" return self._unique_id @property def is_on(self): """Return true if it is on.""" return self._state @property def available(self): """Return True if the device is available.""" return self._device.valve_state != "UNAVAILABLE" def error(self): """Return the error message.""" return self._error_message @property def extra_state_attributes(self): """Return the state attributes of the water valve.""" return { ATTR_ACTIVITY: self._device.valve_activity, ATTR_BATTERY_LEVEL: self._device.battery_level, ATTR_BATTERY_STATE: self._device.battery_state, ATTR_RF_LINK_LEVEL: self._device.rf_link_level, ATTR_RF_LINK_STATE: self._device.rf_link_state, ATTR_LAST_ERROR: self._error_message, } @property def option_smart_watering_duration(self) -> int: return self._options.get( CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION ) def turn_on(self, **kwargs): """Start watering.""" duration = self.option_smart_watering_duration * 60 return asyncio.run_coroutine_threadsafe( self._device.start_seconds_to_override(duration), self.hass.loop ).result() def turn_off(self, **kwargs): """Stop watering.""" return asyncio.run_coroutine_threadsafe( self._device.stop_until_next_task(), self.hass.loop ).result() @property def device_info(self): return { "identifiers": { # Serial numbers are unique identifiers within a specific domain (DOMAIN, self._device.serial) }, "name": self._device.name, "manufacturer": "Gardena", "model": self._device.model_type, } class GardenaPowerSocket(SwitchEntity): """Representation of a Gardena Power Socket.""" def __init__(self, ps): """Initialize the Gardena Power Socket.""" self._device = ps self._name = f"{self._device.name}" self._unique_id = f"{self._device.serial}" self._state = None self._error_message = "" async def async_added_to_hass(self): """Subscribe to events.""" self._device.add_callback(self.update_callback) @property def should_poll(self) -> bool: """No polling needed for a power socket.""" return False def update_callback(self, device): """Call update for Home Assistant when the device is updated.""" self.schedule_update_ha_state(True) async def async_update(self): """Update the states of Gardena devices.""" _LOGGER.debug("Running Gardena update") # Managing state state = self._device.state _LOGGER.debug("Power socket has state %s", state) if state in ["WARNING", "ERROR", "UNAVAILABLE"]: _LOGGER.debug("Power socket has an error") self._state = False self._error_message = self._device.last_error_code else: _LOGGER.debug("Getting Power socket state") activity = self._device.activity self._error_message = "" _LOGGER.debug("Power socket has activity %s", activity) if activity == "OFF": self._state = False elif activity in ["FOREVER_ON", "TIME_LIMITED_ON", "SCHEDULED_ON"]: self._state = True else: _LOGGER.debug("Power socket has none activity") @property def name(self): """Return the name of the device.""" return self._name @property def unique_id(self) -> str: """Return a unique ID.""" return self._unique_id @property def is_on(self): """Return true if it is on.""" return self._state @property def available(self): """Return True if the device is available.""" return self._device.state != "UNAVAILABLE" def error(self): """Return the error message.""" return self._error_message @property def extra_state_attributes(self): """Return the state attributes of the power switch.""" return { ATTR_ACTIVITY: self._device.activity, ATTR_RF_LINK_LEVEL: self._device.rf_link_level, ATTR_RF_LINK_STATE: self._device.rf_link_state, ATTR_LAST_ERROR: self._error_message, } def turn_on(self, **kwargs): """Start watering.""" return asyncio.run_coroutine_threadsafe( self._device.start_override(), self.hass.loop ).result() def turn_off(self, **kwargs): """Stop watering.""" return asyncio.run_coroutine_threadsafe( self._device.stop_until_next_task(), self.hass.loop ).result() @property def device_info(self): return { "identifiers": { # Serial numbers are unique identifiers within a specific domain (DOMAIN, self._device.serial) }, "name": self._device.name, "manufacturer": "Gardena", "model": self._device.model_type, } class GardenaSmartIrrigationControl(SwitchEntity): """Representation of a Gardena Smart Irrigation Control.""" def __init__(self, sic, valve_id, options): """Initialize the Gardena Smart Irrigation Control.""" self._device = sic self._valve_id = valve_id self._options = options self._name = f"{self._device.name} - {self._device.valves[self._valve_id]['name']}" self._unique_id = f"{self._device.serial}-{self._valve_id}" self._state = None self._error_message = "" async def async_added_to_hass(self): """Subscribe to events.""" self._device.add_callback(self.update_callback) @property def should_poll(self) -> bool: """No polling needed for a smart irrigation control.""" return False def update_callback(self, device): """Call update for Home Assistant when the device is updated.""" self.schedule_update_ha_state(True) async def async_update(self): """Update the states of Gardena devices.""" _LOGGER.debug("Running Gardena update") # Managing state valve = self._device.valves[self._valve_id] _LOGGER.debug("Valve has state: %s", valve["state"]) if valve["state"] in ["WARNING", "ERROR", "UNAVAILABLE"]: _LOGGER.debug("Valve has an error") self._state = False self._error_message = valve["last_error_code"] else: _LOGGER.debug("Getting Valve state") activity = valve["activity"] self._error_message = "" _LOGGER.debug("Valve has activity: %s", activity) if activity == "CLOSED": self._state = False elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: self._state = True else: _LOGGER.debug("Valve has unknown activity") @property def name(self): """Return the name of the device.""" return self._name @property def unique_id(self) -> str: """Return a unique ID.""" return self._unique_id @property def is_on(self): """Return true if it is on.""" return self._state @property def available(self): """Return True if the device is available.""" return self._device.valves[self._valve_id]["state"] != "UNAVAILABLE" def error(self): """Return the error message.""" return self._error_message @property def extra_state_attributes(self): """Return the state attributes of the smart irrigation control.""" return { ATTR_ACTIVITY: self._device.valves[self._valve_id]["activity"], ATTR_RF_LINK_LEVEL: self._device.rf_link_level, ATTR_RF_LINK_STATE: self._device.rf_link_state, ATTR_LAST_ERROR: self._error_message, } @property def option_smart_irrigation_duration(self) -> int: return self._options.get( CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION ) def turn_on(self, **kwargs): """Start watering.""" duration = self.option_smart_irrigation_duration * 60 return asyncio.run_coroutine_threadsafe( self._device.start_seconds_to_override(duration, self._valve_id), self.hass.loop ).result() def turn_off(self, **kwargs): """Stop watering.""" return asyncio.run_coroutine_threadsafe( self._device.stop_until_next_task(self._valve_id), self.hass.loop ).result() @property def device_info(self): return { "identifiers": { # Serial numbers are unique identifiers within a specific domain (DOMAIN, self._device.serial) }, "name": self._device.name, "manufacturer": "Gardena", "model": self._device.model_type, }