"""Component to count within automations.""" import logging import voluptuous as vol from homeassistant.const import CONF_ICON, CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) ATTR_INITIAL = "initial" ATTR_STEP = "step" ATTR_MINIMUM = "minimum" ATTR_MAXIMUM = "maximum" VALUE = "value" CONF_INITIAL = "initial" CONF_RESTORE = "restore" CONF_STEP = "step" DEFAULT_INITIAL = 0 DEFAULT_STEP = 1 DOMAIN = "counter" ENTITY_ID_FORMAT = DOMAIN + ".{}" SERVICE_DECREMENT = "decrement" SERVICE_INCREMENT = "increment" SERVICE_RESET = "reset" SERVICE_CONFIGURE = "configure" CONFIG_SCHEMA = vol.Schema( { DOMAIN: cv.schema_with_slug_keys( vol.Any( { vol.Optional(CONF_ICON): cv.icon, vol.Optional( CONF_INITIAL, default=DEFAULT_INITIAL ): cv.positive_int, vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_MAXIMUM, default=None): vol.Any( None, vol.Coerce(int) ), vol.Optional(CONF_MINIMUM, default=None): vol.Any( None, vol.Coerce(int) ), vol.Optional(CONF_RESTORE, default=True): cv.boolean, vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, }, None, ) ) }, extra=vol.ALLOW_EXTRA, ) async def async_setup(hass, config): """Set up the counters.""" component = EntityComponent(_LOGGER, DOMAIN, hass) entities = [] for object_id, cfg in config[DOMAIN].items(): if not cfg: cfg = {} name = cfg.get(CONF_NAME) initial = cfg.get(CONF_INITIAL) restore = cfg.get(CONF_RESTORE) step = cfg.get(CONF_STEP) icon = cfg.get(CONF_ICON) minimum = cfg.get(CONF_MINIMUM) maximum = cfg.get(CONF_MAXIMUM) entities.append( Counter(object_id, name, initial, minimum, maximum, restore, step, icon) ) if not entities: return False component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") component.async_register_entity_service(SERVICE_RESET, {}, "async_reset") component.async_register_entity_service( SERVICE_CONFIGURE, { vol.Optional(ATTR_MINIMUM): vol.Any(None, vol.Coerce(int)), vol.Optional(ATTR_MAXIMUM): vol.Any(None, vol.Coerce(int)), vol.Optional(ATTR_STEP): cv.positive_int, vol.Optional(ATTR_INITIAL): cv.positive_int, vol.Optional(VALUE): cv.positive_int, }, "async_configure", ) await component.async_add_entities(entities) return True class Counter(RestoreEntity): """Representation of a counter.""" def __init__(self, object_id, name, initial, minimum, maximum, restore, step, icon): """Initialize a counter.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name self._restore = restore self._step = step self._state = self._initial = initial self._min = minimum self._max = maximum self._icon = icon @property def should_poll(self): """If entity should be polled.""" return False @property def name(self): """Return name of the counter.""" return self._name @property def icon(self): """Return the icon to be used for this entity.""" return self._icon @property def state(self): """Return the current value of the counter.""" return self._state @property def state_attributes(self): """Return the state attributes.""" ret = {ATTR_INITIAL: self._initial, ATTR_STEP: self._step} if self._min is not None: ret[CONF_MINIMUM] = self._min if self._max is not None: ret[CONF_MAXIMUM] = self._max return ret def compute_next_state(self, state): """Keep the state within the range of min/max values.""" if self._min is not None: state = max(self._min, state) if self._max is not None: state = min(self._max, state) return state async def async_added_to_hass(self): """Call when entity about to be added to Home Assistant.""" await super().async_added_to_hass() # __init__ will set self._state to self._initial, only override # if needed. if self._restore: state = await self.async_get_last_state() if state is not None: self._state = self.compute_next_state(int(state.state)) self._initial = state.attributes.get(ATTR_INITIAL) self._max = state.attributes.get(ATTR_MAXIMUM) self._min = state.attributes.get(ATTR_MINIMUM) self._step = state.attributes.get(ATTR_STEP) async def async_decrement(self): """Decrement the counter.""" self._state = self.compute_next_state(self._state - self._step) await self.async_update_ha_state() async def async_increment(self): """Increment a counter.""" self._state = self.compute_next_state(self._state + self._step) await self.async_update_ha_state() async def async_reset(self): """Reset a counter.""" self._state = self.compute_next_state(self._initial) await self.async_update_ha_state() async def async_configure(self, **kwargs): """Change the counter's settings with a service.""" if CONF_MINIMUM in kwargs: self._min = kwargs[CONF_MINIMUM] if CONF_MAXIMUM in kwargs: self._max = kwargs[CONF_MAXIMUM] if CONF_STEP in kwargs: self._step = kwargs[CONF_STEP] if CONF_INITIAL in kwargs: self._initial = kwargs[CONF_INITIAL] if VALUE in kwargs: self._state = kwargs[VALUE] self._state = self.compute_next_state(self._state) await self.async_update_ha_state()