1 """Component to count within automations."""
3 from __future__
import annotations
6 from typing
import Any, Self
8 import voluptuous
as vol
26 _LOGGER = logging.getLogger(__name__)
28 ATTR_INITIAL =
"initial"
30 ATTR_MINIMUM =
"minimum"
31 ATTR_MAXIMUM =
"maximum"
34 CONF_INITIAL =
"initial"
35 CONF_RESTORE =
"restore"
42 ENTITY_ID_FORMAT = DOMAIN +
".{}"
44 SERVICE_DECREMENT =
"decrement"
45 SERVICE_INCREMENT =
"increment"
46 SERVICE_RESET =
"reset"
47 SERVICE_SET_VALUE =
"set_value"
52 STORAGE_FIELDS: VolDictType = {
53 vol.Optional(CONF_ICON): cv.icon,
54 vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): cv.positive_int,
55 vol.Required(CONF_NAME): vol.All(cv.string, vol.Length(min=1)),
56 vol.Optional(CONF_MAXIMUM, default=
None): vol.Any(
None, vol.Coerce(int)),
57 vol.Optional(CONF_MINIMUM, default=
None): vol.Any(
None, vol.Coerce(int)),
58 vol.Optional(CONF_RESTORE, default=
True): cv.boolean,
59 vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
63 def _none_to_empty_dict[_T](value: _T |
None) -> _T | dict[str, Any]:
69 CONFIG_SCHEMA = vol.Schema(
71 DOMAIN: cv.schema_with_slug_keys(
75 vol.Optional(CONF_ICON): cv.icon,
77 CONF_INITIAL, default=DEFAULT_INITIAL
79 vol.Optional(CONF_NAME): cv.string,
80 vol.Optional(CONF_MAXIMUM, default=
None): vol.Any(
83 vol.Optional(CONF_MINIMUM, default=
None): vol.Any(
86 vol.Optional(CONF_RESTORE, default=
True): cv.boolean,
87 vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
92 extra=vol.ALLOW_EXTRA,
96 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
97 """Set up the counters."""
98 component = EntityComponent[Counter](_LOGGER, DOMAIN, hass)
99 id_manager = collection.IDManager()
101 yaml_collection = collection.YamlCollection(
102 logging.getLogger(f
"{__name__}.yaml_collection"), id_manager
104 collection.sync_entity_lifecycle(
105 hass, DOMAIN, DOMAIN, component, yaml_collection, Counter
109 Store(hass, STORAGE_VERSION, STORAGE_KEY),
112 collection.sync_entity_lifecycle(
113 hass, DOMAIN, DOMAIN, component, storage_collection, Counter
116 await yaml_collection.async_load(
117 [{CONF_ID: id_, **(conf
or {})}
for id_, conf
in config.get(DOMAIN, {}).items()]
119 await storage_collection.async_load()
121 collection.DictStorageCollectionWebsocket(
122 storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
125 component.async_register_entity_service(SERVICE_INCREMENT,
None,
"async_increment")
126 component.async_register_entity_service(SERVICE_DECREMENT,
None,
"async_decrement")
127 component.async_register_entity_service(SERVICE_RESET,
None,
"async_reset")
128 component.async_register_entity_service(
130 {vol.Required(VALUE): cv.positive_int},
138 """Input storage based collection."""
140 CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
143 """Validate the config is valid."""
148 """Suggest an ID based on the config."""
149 return info[CONF_NAME]
152 """Return a new updated data object."""
154 return {CONF_ID: item[CONF_ID]} | update_data
158 """Representation of a counter."""
160 _attr_should_poll: bool =
False
164 """Initialize a counter."""
165 self.
_config_config: ConfigType = config
166 self.
_state_state: int |
None = config[CONF_INITIAL]
170 """Create counter instance from storage."""
171 counter = cls(config)
172 counter.editable =
True
177 """Create counter instance from yaml config."""
178 counter = cls(config)
179 counter.editable =
False
180 counter.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID])
185 """Return name of the counter."""
190 """Return the icon to be used for this entity."""
195 """Return the current value of the counter."""
200 """Return the state attributes."""
202 ATTR_EDITABLE: self.editable,
203 ATTR_INITIAL: self.
_config_config[CONF_INITIAL],
204 ATTR_STEP: self.
_config_config[CONF_STEP],
206 if self.
_config_config[CONF_MINIMUM]
is not None:
207 ret[CONF_MINIMUM] = self.
_config_config[CONF_MINIMUM]
208 if self.
_config_config[CONF_MAXIMUM]
is not None:
209 ret[CONF_MAXIMUM] = self.
_config_config[CONF_MAXIMUM]
214 """Return unique id of the entity."""
215 return self.
_config_config[CONF_ID]
218 """Keep the state within the range of min/max values."""
219 if self.
_config_config[CONF_MINIMUM]
is not None:
220 state =
max(self.
_config_config[CONF_MINIMUM], state)
221 if self.
_config_config[CONF_MAXIMUM]
is not None:
222 state =
min(self.
_config_config[CONF_MAXIMUM], state)
227 """Call when entity about to be added to Home Assistant."""
232 self.
_config_config[CONF_RESTORE]
239 """Decrement the counter."""
245 """Increment a counter."""
251 """Reset a counter."""
257 """Set counter to value."""
258 if (maximum := self.
_config_config.
get(CONF_MAXIMUM))
is not None and value > maximum:
260 f
"Value {value} for {self.entity_id} exceeding the maximum value of {maximum}"
263 if (minimum := self.
_config_config.
get(CONF_MINIMUM))
is not None and value < minimum:
265 f
"Value {value} for {self.entity_id} exceeding the minimum value of {minimum}"
268 if (step := self.
_config_config.
get(CONF_STEP))
is not None and value % step != 0:
270 f
"Value {value} for {self.entity_id} is not a multiple of the step size {step}"
277 """Change the counter's settings WS CRUD."""
dict _update_data(self, dict item, dict update_data)
str _get_suggested_id(self, dict info)
dict _process_create_data(self, dict data)
None async_added_to_hass(self)
dict extra_state_attributes(self)
None __init__(self, ConfigType config)
Self from_yaml(cls, ConfigType config)
None async_increment(self)
int|None compute_next_state(self, int|None state)
None async_decrement(self)
None async_update_config(self, ConfigType config)
Self from_storage(cls, ConfigType config)
None async_set_value(self, int value)
None async_write_ha_state(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
bool async_setup(HomeAssistant hass, ConfigType config)