1 """Analytics helper class for the analytics integration."""
3 from __future__
import annotations
6 from asyncio
import timeout
7 from dataclasses
import asdict
as dataclass_asdict, dataclass
8 from datetime
import datetime
18 DOMAIN
as ENERGY_DOMAIN,
19 is_configured
as energy_is_configured,
22 DOMAIN
as RECORDER_DOMAIN,
23 get_instance
as get_recorder_instance,
38 async_get_integrations,
43 ANALYTICS_ENDPOINT_URL,
44 ANALYTICS_ENDPOINT_URL_DEV,
49 ATTR_AUTOMATION_COUNT,
54 ATTR_CUSTOM_INTEGRATIONS,
59 ATTR_INTEGRATION_COUNT,
61 ATTR_OPERATING_SYSTEM,
85 preferences: dict[str, bool]
89 def from_dict(cls, data: dict[str, Any]) -> AnalyticsData:
90 """Initialize analytics data from a dict."""
99 """Analytics helper class for the analytics integration."""
102 """Initialize the Analytics class."""
103 self.hass: HomeAssistant = hass
106 self.
_store_store = Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
110 """Return the current active preferences."""
111 preferences = self.
_data_data.preferences
113 ATTR_BASE: preferences.get(ATTR_BASE,
False),
114 ATTR_DIAGNOSTICS: preferences.get(ATTR_DIAGNOSTICS,
False),
115 ATTR_USAGE: preferences.get(ATTR_USAGE,
False),
116 ATTR_STATISTICS: preferences.get(ATTR_STATISTICS,
False),
121 """Return bool if the user has made a choice."""
122 return self.
_data_data.onboarded
126 """Return the uuid for the analytics integration."""
127 return self.
_data_data.uuid
131 """Return the endpoint that will receive the payload."""
132 if HA_VERSION.endswith(
"0.dev0"):
134 return ANALYTICS_ENDPOINT_URL_DEV
135 return ANALYTICS_ENDPOINT_URL
139 """Return bool if a supervisor is present."""
143 """Load preferences."""
146 self.
_data_data = AnalyticsData.from_dict(stored)
150 and (supervisor_info := hassio.get_supervisor_info(self.hass))
is not None
154 if supervisor_info[ATTR_DIAGNOSTICS]
and not self.
preferencespreferences.
get(
155 ATTR_DIAGNOSTICS,
False
157 self.
_data_data.preferences[ATTR_DIAGNOSTICS] =
True
158 elif not supervisor_info[ATTR_DIAGNOSTICS]
and self.
preferencespreferences.
get(
159 ATTR_DIAGNOSTICS,
False
161 self.
_data_data.preferences[ATTR_DIAGNOSTICS] =
False
164 """Save preferences."""
166 self.
_data_data.preferences.update(preferences)
167 self.
_data_data.onboarded =
True
172 await hassio.async_update_diagnostics(
173 self.hass, self.
preferencespreferences.
get(ATTR_DIAGNOSTICS,
False)
177 """Send analytics."""
179 supervisor_info =
None
180 operating_system_info: dict[str, Any] = {}
183 LOGGER.debug(
"Nothing to submit")
186 if self.
_data_data.uuid
is None:
187 self.
_data_data.uuid = uuid.uuid4().hex
191 supervisor_info = hassio.get_supervisor_info(hass)
192 operating_system_info = hassio.get_os_info(hass)
or {}
196 custom_integrations = []
197 addons: list[dict[str, Any]] = []
199 ATTR_UUID: self.
uuiduuid,
200 ATTR_VERSION: HA_VERSION,
201 ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE],
204 if supervisor_info
is not None:
205 payload[ATTR_SUPERVISOR] = {
206 ATTR_HEALTHY: supervisor_info[ATTR_HEALTHY],
207 ATTR_SUPPORTED: supervisor_info[ATTR_SUPPORTED],
208 ATTR_ARCH: supervisor_info[ATTR_ARCH],
211 if operating_system_info.get(ATTR_BOARD)
is not None:
212 payload[ATTR_OPERATING_SYSTEM] = {
213 ATTR_BOARD: operating_system_info[ATTR_BOARD],
214 ATTR_VERSION: operating_system_info[ATTR_VERSION],
218 ATTR_STATISTICS,
False
220 ent_reg = er.async_get(hass)
223 yaml_configuration = await conf_util.async_hass_config_yaml(hass)
224 except HomeAssistantError
as err:
228 configuration_set = set(yaml_configuration)
231 for entity
in ent_reg.entities.values()
232 if not entity.disabled
237 enabled_domains = set(configured_integrations)
239 for integration
in configured_integrations.values():
240 if isinstance(integration, IntegrationNotFound):
243 if isinstance(integration, BaseException):
247 integration=integration,
248 yaml_domains=configuration_set,
249 entity_registry_platforms=er_platforms,
253 if not integration.is_built_in:
254 custom_integrations.append(
256 ATTR_DOMAIN: integration.domain,
257 ATTR_VERSION: integration.version,
262 integrations.append(integration.domain)
264 if supervisor_info
is not None:
265 supervisor_client = hassio.get_supervisor_client(hass)
266 installed_addons = await asyncio.gather(
268 supervisor_client.addons.addon_info(addon[ATTR_SLUG])
269 for addon
in supervisor_info[ATTR_ADDONS]
274 ATTR_SLUG: addon.slug,
275 ATTR_PROTECTED: addon.protected,
276 ATTR_VERSION: addon.version,
277 ATTR_AUTO_UPDATE: addon.auto_update,
279 for addon
in installed_addons
283 payload[ATTR_CERTIFICATE] = hass.http.ssl_certificate
is not None
284 payload[ATTR_INTEGRATIONS] = integrations
285 payload[ATTR_CUSTOM_INTEGRATIONS] = custom_integrations
286 if supervisor_info
is not None:
287 payload[ATTR_ADDONS] = addons
289 if ENERGY_DOMAIN
in enabled_domains:
290 payload[ATTR_ENERGY] = {
291 ATTR_CONFIGURED: await energy_is_configured(hass)
294 if RECORDER_DOMAIN
in enabled_domains:
295 instance = get_recorder_instance(hass)
296 engine = instance.database_engine
297 if engine
and engine.version
is not None:
298 payload[ATTR_RECORDER] = {
299 ATTR_ENGINE: engine.dialect.value,
300 ATTR_VERSION: engine.version,
304 payload[ATTR_STATE_COUNT] = hass.states.async_entity_ids_count()
305 payload[ATTR_AUTOMATION_COUNT] = hass.states.async_entity_ids_count(
308 payload[ATTR_INTEGRATION_COUNT] = len(integrations)
309 if supervisor_info
is not None:
310 payload[ATTR_ADDON_COUNT] = len(addons)
311 payload[ATTR_USER_COUNT] = len(
314 for user
in await hass.auth.async_get_users()
315 if not user.system_generated
320 async
with timeout(30):
322 if response.status == 200:
325 "Submitted analytics to Home Assistant servers. "
326 "Information submitted includes %s"
332 "Sending analytics failed with statuscode %s from %s",
337 LOGGER.error(
"Timeout sending analytics to %s", ANALYTICS_ENDPOINT_URL)
338 except aiohttp.ClientError
as err:
340 "Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
346 integration: Integration,
347 yaml_domains: set[str],
348 entity_registry_platforms: set[str],
350 """Return a bool to indicate if this integration should be reported."""
351 if integration.disabled:
356 integration.domain
in yaml_domains
357 or integration.domain
in entity_registry_platforms
362 if not integration.config_flow:
365 entries = self.hass.config_entries.async_entries(integration.domain)
371 if entry.source != SOURCE_IGNORE
and entry.disabled_by
is None
AnalyticsData from_dict(cls, dict[str, Any] data)
None save_preferences(self, dict preferences)
None send_analytics(self, datetime|None _=None)
bool _async_should_report_integration(self, Integration integration, set[str] yaml_domains, set[str] entity_registry_platforms)
None __init__(self, HomeAssistant hass)
web.Response post(self, web.Request request, str config_key)
web.Response get(self, web.Request request, str config_key)
bool is_hassio(HomeAssistant hass)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
None async_load(HomeAssistant hass)
None async_save(self, _T data)
dict[str, Any] async_get_system_info(HomeAssistant hass)
dict[str, Integration|Exception] async_get_integrations(HomeAssistant hass, Iterable[str] domains)
set[str] async_get_loaded_integrations(core.HomeAssistant hass)