Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Kitchen Sink integration contains demonstrations of various odds and ends.
2 
3 This sets up a demo environment of features which are obscure or which represent
4 incorrect behavior, and are thus not wanted in the demo integration.
5 """
6 
7 from __future__ import annotations
8 
9 import datetime
10 from random import random
11 
12 import voluptuous as vol
13 
14 from homeassistant.components.recorder import DOMAIN as RECORDER_DOMAIN, get_instance
15 from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
17  async_add_external_statistics,
18  async_import_statistics,
19  get_last_statistics,
20 )
21 from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
22 from homeassistant.const import Platform, UnitOfEnergy, UnitOfTemperature, UnitOfVolume
23 from homeassistant.core import HomeAssistant, ServiceCall, callback
24 from homeassistant.helpers import config_validation as cv
25 from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
26 from homeassistant.helpers.typing import ConfigType
27 import homeassistant.util.dt as dt_util
28 
29 DOMAIN = "kitchen_sink"
30 
31 
32 COMPONENTS_WITH_DEMO_PLATFORM = [
33  Platform.BUTTON,
34  Platform.IMAGE,
35  Platform.LAWN_MOWER,
36  Platform.LOCK,
37  Platform.NOTIFY,
38  Platform.SENSOR,
39  Platform.SWITCH,
40  Platform.WEATHER,
41 ]
42 
43 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
44 
45 SCHEMA_SERVICE_TEST_SERVICE_1 = vol.Schema(
46  {
47  vol.Required("field_1"): vol.Coerce(int),
48  vol.Required("field_2"): vol.In(["off", "auto", "cool"]),
49  vol.Optional("field_3"): vol.Coerce(int),
50  vol.Optional("field_4"): vol.In(["forwards", "reverse"]),
51  }
52 )
53 
54 
55 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
56  """Set up the demo environment."""
57  hass.async_create_task(
58  hass.config_entries.flow.async_init(
59  DOMAIN, context={"source": SOURCE_IMPORT}, data={}
60  )
61  )
62 
63  @callback
64  def service_handler(call: ServiceCall | None = None) -> None:
65  """Do nothing."""
66 
67  hass.services.async_register(
68  DOMAIN, "test_service_1", service_handler, SCHEMA_SERVICE_TEST_SERVICE_1
69  )
70 
71  return True
72 
73 
74 async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
75  """Set the config entry up."""
76  # Set up demo platforms with config entry
77  await hass.config_entries.async_forward_entry_setups(
78  config_entry, COMPONENTS_WITH_DEMO_PLATFORM
79  )
80 
81  # Create issues
82  _create_issues(hass)
83 
84  # Insert some external statistics
85  if "recorder" in hass.config.components:
86  await _insert_statistics(hass)
87 
88  # Start a reauth flow
89  config_entry.async_start_reauth(hass)
90 
91  return True
92 
93 
94 def _create_issues(hass: HomeAssistant) -> None:
95  """Create some issue registry issues."""
97  hass,
98  DOMAIN,
99  "transmogrifier_deprecated",
100  breaks_in_ha_version="2023.1.1",
101  is_fixable=False,
102  learn_more_url="https://en.wiktionary.org/wiki/transmogrifier",
103  severity=IssueSeverity.WARNING,
104  translation_key="transmogrifier_deprecated",
105  )
106 
108  hass,
109  DOMAIN,
110  "out_of_blinker_fluid",
111  breaks_in_ha_version="2023.1.1",
112  is_fixable=True,
113  learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU",
114  severity=IssueSeverity.CRITICAL,
115  translation_key="out_of_blinker_fluid",
116  )
117 
119  hass,
120  DOMAIN,
121  "unfixable_problem",
122  is_fixable=False,
123  learn_more_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
124  severity=IssueSeverity.WARNING,
125  translation_key="unfixable_problem",
126  )
127 
129  hass,
130  DOMAIN,
131  "bad_psu",
132  is_fixable=True,
133  learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU",
134  severity=IssueSeverity.CRITICAL,
135  translation_key="bad_psu",
136  )
137 
139  hass,
140  DOMAIN,
141  "cold_tea",
142  is_fixable=True,
143  severity=IssueSeverity.WARNING,
144  translation_key="cold_tea",
145  )
146 
147 
149  start: datetime.datetime, end: datetime.datetime, init_value: float, max_diff: float
150 ) -> list[StatisticData]:
151  statistics: list[StatisticData] = []
152  mean = init_value
153  now = start
154  while now < end:
155  mean = mean + random() * max_diff - max_diff / 2
156  statistics.append(
157  {
158  "start": now,
159  "mean": mean,
160  "min": mean - random() * max_diff,
161  "max": mean + random() * max_diff,
162  }
163  )
164  now = now + datetime.timedelta(hours=1)
165 
166  return statistics
167 
168 
170  hass: HomeAssistant,
171  metadata: StatisticMetaData,
172  start: datetime.datetime,
173  end: datetime.datetime,
174  max_diff: float,
175 ) -> None:
176  statistics: list[StatisticData] = []
177  now = start
178  sum_ = 0.0
179  statistic_id = metadata["statistic_id"]
180 
181  last_stats = await get_instance(hass).async_add_executor_job(
182  get_last_statistics, hass, 1, statistic_id, False, {"sum"}
183  )
184  if statistic_id in last_stats:
185  sum_ = last_stats[statistic_id][0]["sum"] or 0
186  while now < end:
187  sum_ = sum_ + random() * max_diff
188  statistics.append(
189  {
190  "start": now,
191  "sum": sum_,
192  }
193  )
194  now = now + datetime.timedelta(hours=1)
195 
196  async_add_external_statistics(hass, metadata, statistics)
197 
198 
199 async def _insert_statistics(hass: HomeAssistant) -> None:
200  """Insert some fake statistics."""
201  now = dt_util.now()
202  yesterday = now - datetime.timedelta(days=1)
203  yesterday_midnight = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
204  today_midnight = yesterday_midnight + datetime.timedelta(days=1)
205 
206  # Fake yesterday's temperatures
207  metadata: StatisticMetaData = {
208  "source": DOMAIN,
209  "name": "Outdoor temperature",
210  "statistic_id": f"{DOMAIN}:temperature_outdoor",
211  "unit_of_measurement": UnitOfTemperature.CELSIUS,
212  "has_mean": True,
213  "has_sum": False,
214  }
215  statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1)
216  async_add_external_statistics(hass, metadata, statistics)
217 
218  # Add external energy consumption in kWh, ~ 12 kWh / day
219  # This should be possible to pick for the energy dashboard
220  metadata = {
221  "source": DOMAIN,
222  "name": "Energy consumption 1",
223  "statistic_id": f"{DOMAIN}:energy_consumption_kwh",
224  "unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
225  "has_mean": False,
226  "has_sum": True,
227  }
228  await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 1)
229 
230  # Add external energy consumption in MWh, ~ 12 kWh / day
231  # This should not be possible to pick for the energy dashboard
232  metadata = {
233  "source": DOMAIN,
234  "name": "Energy consumption 2",
235  "statistic_id": f"{DOMAIN}:energy_consumption_mwh",
236  "unit_of_measurement": UnitOfEnergy.MEGA_WATT_HOUR,
237  "has_mean": False,
238  "has_sum": True,
239  }
241  hass, metadata, yesterday_midnight, today_midnight, 0.001
242  )
243 
244  # Add external gas consumption in m³, ~6 m3/day
245  # This should be possible to pick for the energy dashboard
246  metadata = {
247  "source": DOMAIN,
248  "name": "Gas consumption 1",
249  "statistic_id": f"{DOMAIN}:gas_consumption_m3",
250  "unit_of_measurement": UnitOfVolume.CUBIC_METERS,
251  "has_mean": False,
252  "has_sum": True,
253  }
255  hass, metadata, yesterday_midnight, today_midnight, 0.5
256  )
257 
258  # Add external gas consumption in ft³, ~180 ft3/day
259  # This should not be possible to pick for the energy dashboard
260  metadata = {
261  "source": DOMAIN,
262  "name": "Gas consumption 2",
263  "statistic_id": f"{DOMAIN}:gas_consumption_ft3",
264  "unit_of_measurement": UnitOfVolume.CUBIC_FEET,
265  "has_mean": False,
266  "has_sum": True,
267  }
268  await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 15)
269 
270  # Add some statistics which will raise an issue
271  # Used to raise an issue where the unit has changed to a non volume unit
272  metadata = {
273  "source": RECORDER_DOMAIN,
274  "name": None,
275  "statistic_id": "sensor.statistics_issue_1",
276  "unit_of_measurement": UnitOfVolume.CUBIC_METERS,
277  "has_mean": True,
278  "has_sum": False,
279  }
280  statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1)
281  async_import_statistics(hass, metadata, statistics)
282 
283  # Used to raise an issue where the unit has changed to a different unit
284  metadata = {
285  "source": RECORDER_DOMAIN,
286  "name": None,
287  "statistic_id": "sensor.statistics_issue_2",
288  "unit_of_measurement": "cats",
289  "has_mean": True,
290  "has_sum": False,
291  }
292  statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1)
293  async_import_statistics(hass, metadata, statistics)
294 
295  # Used to raise an issue where state class is not compatible with statistics
296  metadata = {
297  "source": RECORDER_DOMAIN,
298  "name": None,
299  "statistic_id": "sensor.statistics_issue_3",
300  "unit_of_measurement": UnitOfVolume.CUBIC_METERS,
301  "has_mean": True,
302  "has_sum": False,
303  }
304  statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1)
305  async_import_statistics(hass, metadata, statistics)
306 
307  # Used to raise an issue where the sensor is not in the state machine
308  metadata = {
309  "source": RECORDER_DOMAIN,
310  "name": None,
311  "statistic_id": "sensor.statistics_issue_4",
312  "unit_of_measurement": UnitOfVolume.CUBIC_METERS,
313  "has_mean": True,
314  "has_sum": False,
315  }
316  statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1)
317  async_import_statistics(hass, metadata, statistics)
bool async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:74
None _insert_statistics(HomeAssistant hass)
Definition: __init__.py:199
None _insert_sum_statistics(HomeAssistant hass, StatisticMetaData metadata, datetime.datetime start, datetime.datetime end, float max_diff)
Definition: __init__.py:175
list[StatisticData] _generate_mean_statistics(datetime.datetime start, datetime.datetime end, float init_value, float max_diff)
Definition: __init__.py:150
None _create_issues(HomeAssistant hass)
Definition: __init__.py:94
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:55
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69
None async_import_statistics(HomeAssistant hass, StatisticMetaData metadata, Iterable[StatisticData] statistics)
Definition: statistics.py:2298
None async_add_external_statistics(HomeAssistant hass, StatisticMetaData metadata, Iterable[StatisticData] statistics)
Definition: statistics.py:2318
Recorder get_instance(HomeAssistant hass)
Definition: recorder.py:74