Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for statistics."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from datetime import timedelta
7 from typing import Any, cast
8 
9 import voluptuous as vol
10 
11 from homeassistant.components import websocket_api
12 from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
13 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
14 from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
15 from homeassistant.core import HomeAssistant, callback, split_entity_id
16 from homeassistant.exceptions import HomeAssistantError
18  SchemaCommonFlowHandler,
19  SchemaConfigFlowHandler,
20  SchemaFlowError,
21  SchemaFlowFormStep,
22 )
24  BooleanSelector,
25  DurationSelector,
26  DurationSelectorConfig,
27  EntitySelector,
28  EntitySelectorConfig,
29  NumberSelector,
30  NumberSelectorConfig,
31  NumberSelectorMode,
32  SelectSelector,
33  SelectSelectorConfig,
34  SelectSelectorMode,
35  TextSelector,
36 )
37 
38 from . import DOMAIN
39 from .sensor import (
40  CONF_KEEP_LAST_SAMPLE,
41  CONF_MAX_AGE,
42  CONF_PERCENTILE,
43  CONF_PRECISION,
44  CONF_SAMPLES_MAX_BUFFER_SIZE,
45  CONF_STATE_CHARACTERISTIC,
46  DEFAULT_NAME,
47  DEFAULT_PRECISION,
48  STATS_BINARY_SUPPORT,
49  STATS_NUMERIC_SUPPORT,
50  StatisticsSensor,
51 )
52 
53 
54 async def get_state_characteristics(handler: SchemaCommonFlowHandler) -> vol.Schema:
55  """Return schema with state characteristics."""
56  is_binary = (
57  split_entity_id(handler.options[CONF_ENTITY_ID])[0] == BINARY_SENSOR_DOMAIN
58  )
59  if is_binary:
60  options = STATS_BINARY_SUPPORT
61  else:
62  options = STATS_NUMERIC_SUPPORT
63 
64  return vol.Schema(
65  {
66  vol.Required(CONF_STATE_CHARACTERISTIC): SelectSelector(
68  options=list(options),
69  translation_key=CONF_STATE_CHARACTERISTIC,
70  sort=True,
71  mode=SelectSelectorMode.DROPDOWN,
72  )
73  ),
74  }
75  )
76 
77 
78 async def validate_options(
79  handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
80 ) -> dict[str, Any]:
81  """Validate options selected."""
82  if (
83  user_input.get(CONF_SAMPLES_MAX_BUFFER_SIZE) is None
84  and user_input.get(CONF_MAX_AGE) is None
85  ):
86  raise SchemaFlowError("missing_max_age_or_sampling_size")
87 
88  if (
89  user_input.get(CONF_KEEP_LAST_SAMPLE) is True
90  and user_input.get(CONF_MAX_AGE) is None
91  ):
92  raise SchemaFlowError("missing_keep_last_sample")
93 
94  handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
95 
96  return user_input
97 
98 
99 DATA_SCHEMA_SETUP = vol.Schema(
100  {
101  vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
102  vol.Required(CONF_ENTITY_ID): EntitySelector(
103  EntitySelectorConfig(domain=[BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN])
104  ),
105  }
106 )
107 DATA_SCHEMA_OPTIONS = vol.Schema(
108  {
109  vol.Optional(CONF_SAMPLES_MAX_BUFFER_SIZE): NumberSelector(
110  NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
111  ),
112  vol.Optional(CONF_MAX_AGE): DurationSelector(
113  DurationSelectorConfig(enable_day=False, allow_negative=False)
114  ),
115  vol.Optional(CONF_KEEP_LAST_SAMPLE, default=False): BooleanSelector(),
116  vol.Optional(CONF_PERCENTILE, default=50): NumberSelector(
117  NumberSelectorConfig(min=1, max=99, step=1, mode=NumberSelectorMode.BOX)
118  ),
119  vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): NumberSelector(
120  NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
121  ),
122  }
123 )
124 
125 CONFIG_FLOW = {
126  "user": SchemaFlowFormStep(
127  schema=DATA_SCHEMA_SETUP,
128  next_step="state_characteristic",
129  ),
130  "state_characteristic": SchemaFlowFormStep(
131  schema=get_state_characteristics, next_step="options"
132  ),
133  "options": SchemaFlowFormStep(
134  schema=DATA_SCHEMA_OPTIONS,
135  validate_user_input=validate_options,
136  preview="statistics",
137  ),
138 }
139 OPTIONS_FLOW = {
140  "init": SchemaFlowFormStep(
141  DATA_SCHEMA_OPTIONS,
142  validate_user_input=validate_options,
143  preview="statistics",
144  ),
145 }
146 
147 
149  """Handle a config flow for Statistics."""
150 
151  config_flow = CONFIG_FLOW
152  options_flow = OPTIONS_FLOW
153 
154  def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
155  """Return config entry title."""
156  return cast(str, options[CONF_NAME])
157 
158  @staticmethod
159  async def async_setup_preview(hass: HomeAssistant) -> None:
160  """Set up preview WS API."""
161  websocket_api.async_register_command(hass, ws_start_preview)
162 
163 
164 @websocket_api.websocket_command( { vol.Required("type"): "statistics/start_preview",
165  vol.Required("flow_id"): str,
166  vol.Required("flow_type"): vol.Any("config_flow", "options_flow"),
167  vol.Required("user_input"): dict,
168  }
169 )
170 @websocket_api.async_response
171 async def ws_start_preview(
172  hass: HomeAssistant,
174  msg: dict[str, Any],
175 ) -> None:
176  """Generate a preview."""
177 
178  if msg["flow_type"] == "config_flow":
179  flow_status = hass.config_entries.flow.async_get(msg["flow_id"])
180  flow_sets = hass.config_entries.flow._handler_progress_index.get( # noqa: SLF001
181  flow_status["handler"]
182  )
183  options = {}
184  assert flow_sets
185  for active_flow in flow_sets:
186  options = active_flow._common_handler.options # type: ignore [attr-defined] # noqa: SLF001
187  config_entry = hass.config_entries.async_get_entry(flow_status["handler"])
188  entity_id = options[CONF_ENTITY_ID]
189  name = options[CONF_NAME]
190  state_characteristic = options[CONF_STATE_CHARACTERISTIC]
191  else:
192  flow_status = hass.config_entries.options.async_get(msg["flow_id"])
193  config_entry = hass.config_entries.async_get_entry(flow_status["handler"])
194  if not config_entry:
195  raise HomeAssistantError("Config entry not found")
196  entity_id = config_entry.options[CONF_ENTITY_ID]
197  name = config_entry.options[CONF_NAME]
198  state_characteristic = config_entry.options[CONF_STATE_CHARACTERISTIC]
199 
200  @callback
201  def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
202  """Forward config entry state events to websocket."""
203  connection.send_message(
204  websocket_api.event_message(
205  msg["id"], {"attributes": attributes, "state": state}
206  )
207  )
208 
209  sampling_size = msg["user_input"].get(CONF_SAMPLES_MAX_BUFFER_SIZE)
210  if sampling_size:
211  sampling_size = int(sampling_size)
212 
213  max_age = None
214  if max_age_input := msg["user_input"].get(CONF_MAX_AGE):
215  max_age = timedelta(
216  hours=max_age_input["hours"],
217  minutes=max_age_input["minutes"],
218  seconds=max_age_input["seconds"],
219  )
220  preview_entity = StatisticsSensor(
221  hass,
222  entity_id,
223  name,
224  None,
225  state_characteristic,
226  sampling_size,
227  max_age,
228  msg["user_input"].get(CONF_KEEP_LAST_SAMPLE),
229  msg["user_input"].get(CONF_PRECISION),
230  msg["user_input"].get(CONF_PERCENTILE),
231  )
232  preview_entity.hass = hass
233 
234  connection.send_result(msg["id"])
235  connection.subscriptions[msg["id"]] = await preview_entity.async_start_preview(
236  async_preview_updated
237  )
238 
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None ws_start_preview(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Definition: config_flow.py:177
dict[str, Any] validate_options(SchemaCommonFlowHandler handler, dict[str, Any] user_input)
Definition: config_flow.py:80
vol.Schema get_state_characteristics(SchemaCommonFlowHandler handler)
Definition: config_flow.py:54
tuple[str, str] split_entity_id(str entity_id)
Definition: core.py:214