From d9f1450ee64e6f927ae6826e34cd04be42c084ad Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 23 Jan 2024 10:32:31 +0100 Subject: [PATCH] Add Homeassistant Analytics Insights integration (#107634) * Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Fix feedback * Fix test * Update conftest.py * Add some testcases * Make code clear * log exception * Bump python-homeassistant-analytics to 0.2.1 * Bump python-homeassistant-analytics to 0.3.0 * Change domain to homeassistant_analytics_consumer * Add integration name to config flow selector * Update homeassistant/components/homeassistant_analytics_consumer/manifest.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Fix hassfest * Apply suggestions from code review Co-authored-by: Robert Resch * Bump python-homeassistant-analytics to 0.4.0 * Rename to Home Assistant Analytics Insights * Update homeassistant/components/analytics_insights/config_flow.py Co-authored-by: Robert Resch * Update homeassistant/components/analytics_insights/manifest.json Co-authored-by: Robert Resch * Rename to Home Assistant Analytics Insights * add test * Fallback to 0 when there is no data found * Allow to select any integration * Fix tests * Fix tests * Update tests/components/analytics_insights/conftest.py Co-authored-by: Robert Resch * Update tests/components/analytics_insights/test_sensor.py Co-authored-by: Robert Resch * Fix format * Fix tests --------- Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> Co-authored-by: Robert Resch --- .strict-typing | 1 + CODEOWNERS | 2 + .../components/analytics_insights/__init__.py | 58 + .../analytics_insights/config_flow.py | 74 + .../components/analytics_insights/const.py | 8 + .../analytics_insights/coordinator.py | 53 + .../analytics_insights/manifest.json | 11 + .../components/analytics_insights/sensor.py | 62 + .../analytics_insights/strings.json | 18 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + mypy.ini | 10 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + .../components/analytics_insights/__init__.py | 11 + .../components/analytics_insights/conftest.py | 54 + .../fixtures/current_data.json | 2516 +++++++++++++++++ .../fixtures/integrations.json | 9 + .../snapshots/test_sensor.ambr | 142 + .../analytics_insights/test_config_flow.py | 70 + .../analytics_insights/test_init.py | 28 + .../analytics_insights/test_sensor.py | 86 + 22 files changed, 3226 insertions(+) create mode 100644 homeassistant/components/analytics_insights/__init__.py create mode 100644 homeassistant/components/analytics_insights/config_flow.py create mode 100644 homeassistant/components/analytics_insights/const.py create mode 100644 homeassistant/components/analytics_insights/coordinator.py create mode 100644 homeassistant/components/analytics_insights/manifest.json create mode 100644 homeassistant/components/analytics_insights/sensor.py create mode 100644 homeassistant/components/analytics_insights/strings.json create mode 100644 tests/components/analytics_insights/__init__.py create mode 100644 tests/components/analytics_insights/conftest.py create mode 100644 tests/components/analytics_insights/fixtures/current_data.json create mode 100644 tests/components/analytics_insights/fixtures/integrations.json create mode 100644 tests/components/analytics_insights/snapshots/test_sensor.ambr create mode 100644 tests/components/analytics_insights/test_config_flow.py create mode 100644 tests/components/analytics_insights/test_init.py create mode 100644 tests/components/analytics_insights/test_sensor.py diff --git a/.strict-typing b/.strict-typing index d528484cc98..8ffb02024c9 100644 --- a/.strict-typing +++ b/.strict-typing @@ -69,6 +69,7 @@ homeassistant.components.ambient_station.* homeassistant.components.amcrest.* homeassistant.components.ampio.* homeassistant.components.analytics.* +homeassistant.components.analytics_insights.* homeassistant.components.android_ip_webcam.* homeassistant.components.androidtv.* homeassistant.components.androidtv_remote.* diff --git a/CODEOWNERS b/CODEOWNERS index f4a1d72edc0..8ad0c7e5273 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -76,6 +76,8 @@ build.json @home-assistant/supervisor /homeassistant/components/amcrest/ @flacjacket /homeassistant/components/analytics/ @home-assistant/core @ludeeus /tests/components/analytics/ @home-assistant/core @ludeeus +/homeassistant/components/analytics_insights/ @joostlek +/tests/components/analytics_insights/ @joostlek /homeassistant/components/android_ip_webcam/ @engrbm87 /tests/components/android_ip_webcam/ @engrbm87 /homeassistant/components/androidtv/ @JeffLIrion @ollo69 diff --git a/homeassistant/components/analytics_insights/__init__.py b/homeassistant/components/analytics_insights/__init__.py new file mode 100644 index 00000000000..2078d9715f4 --- /dev/null +++ b/homeassistant/components/analytics_insights/__init__.py @@ -0,0 +1,58 @@ +"""The Homeassistant Analytics integration.""" +from __future__ import annotations + +from dataclasses import dataclass + +from python_homeassistant_analytics import HomeassistantAnalyticsClient + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN +from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator + +PLATFORMS: list[Platform] = [Platform.SENSOR] + + +@dataclass(frozen=True) +class AnalyticsData: + """Analytics data class.""" + + coordinator: HomeassistantAnalyticsDataUpdateCoordinator + names: dict[str, str] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Homeassistant Analytics from a config entry.""" + client = HomeassistantAnalyticsClient(session=async_get_clientsession(hass)) + + integrations = await client.get_integrations() + + names = {} + for integration in entry.options[CONF_TRACKED_INTEGRATIONS]: + if integration not in integrations: + names[integration] = integration + continue + names[integration] = integrations[integration].title + + coordinator = HomeassistantAnalyticsDataUpdateCoordinator(hass, client) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnalyticsData( + coordinator=coordinator, names=names + ) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py new file mode 100644 index 00000000000..afa6b2bac38 --- /dev/null +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -0,0 +1,74 @@ +"""Config flow for Homeassistant Analytics integration.""" +from __future__ import annotations + +from typing import Any + +from python_homeassistant_analytics import ( + HomeassistantAnalyticsClient, + HomeassistantAnalyticsConnectionError, +) +from python_homeassistant_analytics.models import IntegrationType +import voluptuous as vol + +from homeassistant.config_entries import ConfigFlow +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.selector import ( + SelectOptionDict, + SelectSelector, + SelectSelectorConfig, +) + +from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER + +INTEGRATION_TYPES_WITHOUT_ANALYTICS = ( + IntegrationType.BRAND, + IntegrationType.ENTITY, + IntegrationType.VIRTUAL, +) + + +class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Homeassistant Analytics.""" + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + self._async_abort_entries_match() + if user_input: + return self.async_create_entry( + title="Home Assistant Analytics Insights", data={}, options=user_input + ) + + client = HomeassistantAnalyticsClient( + session=async_get_clientsession(self.hass) + ) + try: + integrations = await client.get_integrations() + except HomeassistantAnalyticsConnectionError: + LOGGER.exception("Error connecting to Home Assistant analytics") + return self.async_abort(reason="cannot_connect") + + options = [ + SelectOptionDict( + value=domain, + label=integration.title, + ) + for domain, integration in integrations.items() + if integration.integration_type not in INTEGRATION_TYPES_WITHOUT_ANALYTICS + ] + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector( + SelectSelectorConfig( + options=options, + multiple=True, + sort=True, + ) + ), + } + ), + ) diff --git a/homeassistant/components/analytics_insights/const.py b/homeassistant/components/analytics_insights/const.py new file mode 100644 index 00000000000..3b9bf01d11e --- /dev/null +++ b/homeassistant/components/analytics_insights/const.py @@ -0,0 +1,8 @@ +"""Constants for the Homeassistant Analytics integration.""" +import logging + +DOMAIN = "analytics_insights" + +CONF_TRACKED_INTEGRATIONS = "tracked_integrations" + +LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/analytics_insights/coordinator.py b/homeassistant/components/analytics_insights/coordinator.py new file mode 100644 index 00000000000..f8eefe7db27 --- /dev/null +++ b/homeassistant/components/analytics_insights/coordinator.py @@ -0,0 +1,53 @@ +"""DataUpdateCoordinator for the Homeassistant Analytics integration.""" +from __future__ import annotations + +from datetime import timedelta + +from python_homeassistant_analytics import ( + HomeassistantAnalyticsClient, + HomeassistantAnalyticsConnectionError, + HomeassistantAnalyticsNotModifiedError, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER + + +class HomeassistantAnalyticsDataUpdateCoordinator( + DataUpdateCoordinator[dict[str, int]] +): + """A Homeassistant Analytics Data Update Coordinator.""" + + config_entry: ConfigEntry + + def __init__( + self, hass: HomeAssistant, client: HomeassistantAnalyticsClient + ) -> None: + """Initialize the Homeassistant Analytics data coordinator.""" + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=timedelta(hours=12), + ) + self._client = client + self._tracked_integrations = self.config_entry.options[ + CONF_TRACKED_INTEGRATIONS + ] + + async def _async_update_data(self) -> dict[str, int]: + try: + data = await self._client.get_current_analytics() + except HomeassistantAnalyticsConnectionError as err: + raise UpdateFailed( + "Error communicating with Homeassistant Analytics" + ) from err + except HomeassistantAnalyticsNotModifiedError: + return self.data + return { + integration: data.integrations.get(integration, 0) + for integration in self._tracked_integrations + } diff --git a/homeassistant/components/analytics_insights/manifest.json b/homeassistant/components/analytics_insights/manifest.json new file mode 100644 index 00000000000..0dfb1396a72 --- /dev/null +++ b/homeassistant/components/analytics_insights/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "analytics_insights", + "name": "Home Assistant Analytics Insights", + "codeowners": ["@joostlek"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/analytics_insights", + "integration_type": "service", + "iot_class": "cloud_polling", + "loggers": ["python_homeassistant_analytics"], + "requirements": ["python-homeassistant-analytics==0.5.0"] +} diff --git a/homeassistant/components/analytics_insights/sensor.py b/homeassistant/components/analytics_insights/sensor.py new file mode 100644 index 00000000000..f8a8b244c60 --- /dev/null +++ b/homeassistant/components/analytics_insights/sensor.py @@ -0,0 +1,62 @@ +"""Sensor for Home Assistant analytics.""" +from __future__ import annotations + +from homeassistant.components.sensor import SensorEntity, SensorStateClass +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import AnalyticsData +from .const import DOMAIN +from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Initialize the entries.""" + + analytics_data: AnalyticsData = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + HomeassistantAnalyticsSensor( + analytics_data.coordinator, + integration_domain, + analytics_data.names[integration_domain], + ) + for integration_domain in analytics_data.coordinator.data + ) + + +class HomeassistantAnalyticsSensor( + CoordinatorEntity[HomeassistantAnalyticsDataUpdateCoordinator], SensorEntity +): + """Home Assistant Analytics Sensor.""" + + _attr_has_entity_name = True + _attr_state_class = SensorStateClass.TOTAL + _attr_native_unit_of_measurement = "active installations" + + def __init__( + self, + coordinator: HomeassistantAnalyticsDataUpdateCoordinator, + integration_domain: str, + name: str, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self._attr_name = name + self._attr_unique_id = f"core_{integration_domain}_active_installations" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, DOMAIN)}, + entry_type=DeviceEntryType.SERVICE, + ) + self._integration_domain = integration_domain + + @property + def native_value(self) -> int | None: + """Return the state of the sensor.""" + return self.coordinator.data.get(self._integration_domain) diff --git a/homeassistant/components/analytics_insights/strings.json b/homeassistant/components/analytics_insights/strings.json new file mode 100644 index 00000000000..c6890524a6b --- /dev/null +++ b/homeassistant/components/analytics_insights/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "user": { + "data": { + "tracked_integrations": "Integrations" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 91a572e1514..5e88b2f9e8a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -42,6 +42,7 @@ FLOWS = { "amberelectric", "ambiclimate", "ambient_station", + "analytics_insights", "android_ip_webcam", "androidtv", "androidtv_remote", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 1cb43016efc..bbc5476896a 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -255,6 +255,12 @@ "config_flow": false, "iot_class": "cloud_polling" }, + "analytics_insights": { + "name": "Home Assistant Analytics Insights", + "integration_type": "service", + "config_flow": true, + "iot_class": "cloud_polling" + }, "android_ip_webcam": { "name": "Android IP Webcam", "integration_type": "hub", diff --git a/mypy.ini b/mypy.ini index f3e3df193d3..68e40b51c50 100644 --- a/mypy.ini +++ b/mypy.ini @@ -450,6 +450,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.analytics_insights.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.android_ip_webcam.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 48cf6be7193..9a02be3602c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2197,6 +2197,9 @@ python-gc100==1.0.3a0 # homeassistant.components.gitlab_ci python-gitlab==1.6.0 +# homeassistant.components.analytics_insights +python-homeassistant-analytics==0.5.0 + # homeassistant.components.homewizard python-homewizard-energy==4.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a0f1ade9ee0..9037316fbae 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1673,6 +1673,9 @@ python-ecobee-api==0.2.17 # homeassistant.components.fully_kiosk python-fullykiosk==0.0.12 +# homeassistant.components.analytics_insights +python-homeassistant-analytics==0.5.0 + # homeassistant.components.homewizard python-homewizard-energy==4.1.0 diff --git a/tests/components/analytics_insights/__init__.py b/tests/components/analytics_insights/__init__.py new file mode 100644 index 00000000000..9e20a72c438 --- /dev/null +++ b/tests/components/analytics_insights/__init__.py @@ -0,0 +1,11 @@ +"""Tests for the Homeassistant Analytics integration.""" +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/analytics_insights/conftest.py b/tests/components/analytics_insights/conftest.py new file mode 100644 index 00000000000..a1a32cb3f74 --- /dev/null +++ b/tests/components/analytics_insights/conftest.py @@ -0,0 +1,54 @@ +"""Common fixtures for the Homeassistant Analytics tests.""" +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest +from python_homeassistant_analytics import CurrentAnalytics +from python_homeassistant_analytics.models import Integration + +from homeassistant.components.analytics_insights import DOMAIN +from homeassistant.components.analytics_insights.const import CONF_TRACKED_INTEGRATIONS + +from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.analytics_insights.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture +def mock_analytics_client() -> Generator[AsyncMock, None, None]: + """Mock a Homeassistant Analytics client.""" + with patch( + "homeassistant.components.analytics_insights.HomeassistantAnalyticsClient", + autospec=True, + ) as mock_client, patch( + "homeassistant.components.analytics_insights.config_flow.HomeassistantAnalyticsClient", + new=mock_client, + ): + client = mock_client.return_value + client.get_current_analytics.return_value = CurrentAnalytics.from_json( + load_fixture("analytics_insights/current_data.json") + ) + integrations = load_json_object_fixture("analytics_insights/integrations.json") + client.get_integrations.return_value = { + key: Integration.from_dict(value) for key, value in integrations.items() + } + yield client + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Mock a config entry.""" + return MockConfigEntry( + domain=DOMAIN, + title="Homeassistant Analytics", + data={}, + options={CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify", "myq"]}, + ) diff --git a/tests/components/analytics_insights/fixtures/current_data.json b/tests/components/analytics_insights/fixtures/current_data.json new file mode 100644 index 00000000000..c652a8c0154 --- /dev/null +++ b/tests/components/analytics_insights/fixtures/current_data.json @@ -0,0 +1,2516 @@ +{ + "last_updated": 1704885791095, + "countries": { + "US": 53255, + "DE": 43792, + "IN": 1135, + "CA": 8212, + "FI": 2859, + "IT": 11088, + "GB": 18165, + "SE": 8389, + "AU": 8996, + "PL": 8283, + "CH": 3845, + "BG": 798, + "NO": 3985, + "NL": 20162, + "CN": 13037, + "FR": 15643, + "RO": 2769, + "GR": 1255, + "GA": 4, + "HU": 2771, + "HK": 1777, + "UA": 2521, + "ES": 8712, + "CZ": 4694, + "BR": 4255, + "SK": 1212, + "RU": 9289, + "PT": 3334, + "ZA": 2642, + "BE": 6386, + "DK": 5059, + "CY": 181, + "AT": 4164, + "IL": 1132, + "IS": 262, + "LU": 281, + "AR": 842, + "BJ": 3, + "KR": 1323, + "PA": 76, + "ID": 772, + "IE": 1205, + "TW": 2187, + "SG": 961, + "BA": 49, + "TH": 1995, + "KZ": 227, + "VN": 998, + "HR": 518, + "BH": 53, + "CU": 18, + "BY": 507, + "MT": 109, + "MX": 1147, + "TR": 904, + "CO": 361, + "LT": 789, + "SI": 718, + "RE": 86, + "LB": 84, + "DO": 126, + "AZ": 36, + "NZ": 1692, + "SA": 274, + "AE": 310, + "JP": 901, + "LK": 61, + "EE": 693, + "HN": 31, + "EC": 117, + "CL": 615, + "LV": 450, + "MY": 639, + "UY": 167, + "BF": 4, + "QA": 47, + "PR": 120, + "SX": 3, + "NG": 80, + "BM": 10, + "MQ": 14, + "NA": 36, + "LY": 11, + "JM": 32, + "EG": 273, + "KY": 6, + "RS": 312, + "NP": 24, + "PK": 91, + "MD": 122, + "JE": 23, + "MK": 57, + "PE": 125, + "TT": 59, + "ZM": 10, + "PY": 60, + "PH": 348, + "IM": 38, + "LS": 4, + "ME": 30, + "TG": 15, + "IR": 66, + "GT": 30, + "MO": 55, + "IQ": 64, + "GI": 6, + "MZ": 15, + "GE": 77, + "CR": 161, + "MM": 24, + "TJ": 37, + "UZ": 85, + "AD": 10, + "AM": 73, + "PF": 12, + "CI": 16, + "KG": 30, + "BQ": 7, + "DZ": 58, + "GG": 17, + "BZ": 4, + "JO": 57, + "MV": 13, + "SV": 17, + "VE": 39, + "YE": 6, + "MA": 143, + "MU": 21, + "OM": 49, + "NC": 29, + "BO": 54, + "XK": 20, + "KW": 38, + "GU": 5, + "BS": 12, + "GP": 27, + "MN": 19, + "ET": 13, + "TN": 60, + "FO": 18, + "ZW": 20, + "KE": 40, + "LI": 17, + "BB": 15, + "KH": 38, + "CW": 26, + "BD": 73, + "SC": 3, + "SL": 1, + "SM": 12, + "GH": 52, + "PS": 20, + "PG": 3, + "AO": 11, + "FJ": 4, + "AX": 17, + "NI": 8, + "AW": 17, + "GD": 2, + "SN": 13, + "LA": 14, + "MG": 8, + "AL": 17, + "GF": 6, + "CG": 2, + "GY": 4, + "SR": 17, + "TC": 1, + "UG": 12, + "GL": 2, + "VC": 3, + "IO": 1, + "TZ": 8, + "RW": 3, + "CV": 7, + "LC": 8, + "YT": 2, + "ML": 2, + "AG": 7, + "MF": 1, + "BN": 12, + "HT": 3, + "VI": 2, + "MW": 4, + "BT": 3, + "WS": 1, + "VG": 3, + "SY": 2, + "BW": 8, + "CM": 4, + "DJ": 1, + "TL": 1, + "SS": 1, + "TM": 1, + "MC": 2, + "T1": 1, + "AI": 1, + "FM": 1, + "CD": 2 + }, + "installation_types": { + "os": 223469, + "container": 52434, + "core": 9752, + "supervised": 15221, + "unsupported_container": 6100, + "unknown": 3424 + }, + "active_installations": 310400, + "avg_users": 1, + "avg_automations": 12, + "avg_integrations": 27, + "avg_addons": 6, + "avg_states": 208, + "integrations": { + "script": 246128, + "cpuspeed": 6523, + "apple_tv": 31246, + "hue": 39499, + "radio_browser": 176070, + "scene": 242257, + "dlna_dmr": 68815, + "google_translate": 129024, + "frontend": 204291, + "knx": 2931, + "hassio": 193035, + "cast": 124532, + "webostv": 28423, + "thread": 56573, + "met": 193200, + "person": 248256, + "openai_conversation": 5151, + "androidtv_remote": 42649, + "sun": 247811, + "mobile_app": 202779, + "default_config": 242787, + "automation": 247261, + "homekit": 46540, + "google": 17436, + "homekit_controller": 32230, + "tuya": 48547, + "xbox": 6322, + "shopping_list": 96171, + "cloud": 88674, + "zone": 90365, + "group": 88150, + "generic": 17347, + "timer": 49307, + "persistent_notification": 27681, + "notify": 58782, + "diagnostics": 24576, + "auth": 27797, + "raspberry_pi": 5212, + "button": 16379, + "recorder": 61879, + "energy": 74107, + "stream": 32220, + "zeroconf": 30200, + "dhcp": 28169, + "input_number": 67186, + "hikvision": 1167, + "samsungtv": 40354, + "lock": 12257, + "repairs": 21514, + "device_automation": 27672, + "websocket_api": 28669, + "siren": 10473, + "input_text": 51877, + "nest": 12686, + "media_player": 32265, + "schedule": 32924, + "number": 15660, + "logger": 41301, + "hardware": 21352, + "stt": 8616, + "image_upload": 16394, + "file_upload": 20736, + "onboarding": 27672, + "search": 27672, + "input_select": 55495, + "conversation": 18537, + "input_button": 45918, + "usb": 27664, + "map": 31265, + "camera": 29502, + "cover": 22675, + "vacuum": 11051, + "ipp": 68927, + "ffmpeg": 21083, + "blueprint": 27578, + "tplink": 23668, + "update": 15896, + "assist_pipeline": 7784, + "counter": 39800, + "weather": 23585, + "input_datetime": 55333, + "media_source": 30841, + "input_boolean": 87498, + "binary_sensor": 50815, + "humidifier": 10005, + "tag": 28172, + "system_log": 29282, + "network": 27897, + "ssdp": 29607, + "alarm_control_panel": 20449, + "system_health": 33296, + "application_credentials": 22649, + "bluetooth_adapters": 8357, + "device_tracker": 33009, + "zwave_js": 24553, + "http": 89834, + "history": 35110, + "climate": 31824, + "sensor": 101798, + "my": 32475, + "light": 36609, + "homeassistant": 108327, + "webhook": 28963, + "trace": 27575, + "bluetooth": 124139, + "fan": 17378, + "upnp": 84171, + "logbook": 35151, + "rpi_power": 86861, + "config": 33096, + "analytics": 27699, + "homeassistant_alerts": 22022, + "lovelace": 37644, + "remote": 8677, + "switch": 50974, + "select": 14321, + "api": 32847, + "google_assistant": 14358, + "tts": 140044, + "co2signal": 24353, + "ibeacon": 46067, + "dlna_dms": 55732, + "accuweather": 11610, + "forecast_solar": 25140, + "sharkiq": 1272, + "derivative": 4282, + "google_assistant_sdk": 5727, + "wyoming": 13699, + "oralb": 7812, + "rhasspy": 1020, + "braviatv": 9300, + "utility_meter": 27735, + "shell_command": 10702, + "ecobee": 7789, + "govee_ble": 3648, + "lifx": 4829, + "sma": 1717, + "esphome": 56656, + "wake_on_lan": 19186, + "ovo_energy": 272, + "generic_thermostat": 3333, + "asuswrt": 2656, + "sonos": 36389, + "homeassistant_sky_connect": 13911, + "local_calendar": 17171, + "nfandroidtv": 2838, + "workday": 12748, + "trafikverket_weatherstation": 440, + "jellyfin": 2079, + "inkbird": 1768, + "nut": 8750, + "mqtt": 108113, + "min_max": 14592, + "smhi": 1917, + "zha": 58308, + "roborock": 7363, + "template": 50756, + "python_script": 7050, + "synology_dsm": 26998, + "shelly": 44653, + "imap": 789, + "spotify": 24388, + "enphase_envoy": 2833, + "ld2410_ble": 935, + "command_line": 10749, + "mjpeg": 6094, + "open_meteo": 2590, + "intent_script": 1526, + "local_todo": 8378, + "ping": 13284, + "deconz": 5495, + "telegram_bot": 14008, + "systemmonitor": 32242, + "fritzbox": 14039, + "fully_kiosk": 6518, + "fritz": 18266, + "androidtv": 8058, + "brother": 19543, + "xiaomi_miio": 16242, + "influxdb": 21696, + "home_connect": 5784, + "edl21": 288, + "text": 5868, + "tasmota": 26322, + "onvif": 9899, + "blink": 5458, + "time_date": 1168, + "panel_custom": 7196, + "wiz": 8421, + "plex": 13197, + "pi_hole": 7098, + "devolo_home_network": 1472, + "syncthru": 2507, + "ring": 13663, + "tado": 8639, + "broadlink": 18804, + "yeelight": 9919, + "goodwe": 1302, + "integration": 13884, + "wled": 17444, + "prometheus": 1447, + "reolink": 12919, + "water_heater": 3028, + "switch_as_x": 36720, + "mvglive": 1, + "coronavirus": 256, + "openweathermap": 21362, + "calendar": 4770, + "smartthings": 10651, + "panel_iframe": 7752, + "version": 8944, + "adguard": 13192, + "unifi": 16589, + "xiaomi_aqara": 2887, + "unifiprotect": 10382, + "speedtestdotnet": 23310, + "twilio": 984, + "radarr": 1962, + "twinkly": 3075, + "sonarr": 2555, + "cloudflare": 1916, + "matter": 17099, + "fronius": 3296, + "doorbird": 979, + "dsmr": 4086, + "moon": 12149, + "roku": 10092, + "airnow": 799, + "abode": 531, + "flux_led": 5932, + "bond": 2105, + "opower": 1282, + "yolink": 990, + "philips_js": 3285, + "cert_expiry": 4618, + "harmony": 10081, + "alexa": 7705, + "rfxtrx": 2359, + "vesync": 3161, + "wemo": 7182, + "ios": 12512, + "xiaomi_ble": 15798, + "lutron_caseta": 5293, + "qingping": 698, + "profiler": 1696, + "ifttt": 7056, + "youtube": 339, + "vizio": 2630, + "kodi": 5174, + "foscam": 1652, + "overkiz": 4492, + "rest_command": 10337, + "yamaha_musiccast": 4436, + "tibber": 5121, + "surepetcare": 838, + "whirlpool": 231, + "freebox": 2558, + "tile": 3222, + "netatmo": 11034, + "github": 3137, + "meteo_france": 3916, + "homewizard": 5398, + "elgato": 1956, + "lastfm": 302, + "history_stats": 865, + "owntracks": 2356, + "backup": 11951, + "aussie_broadband": 506, + "image": 11833, + "buienradar": 6651, + "sql": 4960, + "mikrotik": 1761, + "local_ip": 15081, + "uptime": 12332, + "otbr": 5596, + "octoprint": 9560, + "aemet": 1390, + "melcloud": 2786, + "heos": 6197, + "denonavr": 10899, + "image_processing": 1319, + "panasonic_viera": 1191, + "lacrosse_view": 181, + "starlink": 444, + "google_mail": 762, + "tailscale": 737, + "icloud": 5767, + "nmap_tracker": 5171, + "dnsip": 4586, + "konnected": 1305, + "tesla_wall_connector": 2325, + "roomba": 8552, + "decora_wifi": 622, + "ambient_station": 1249, + "life360": 2360, + "scrape": 2436, + "threshold": 7052, + "august": 4752, + "proximity": 4814, + "srp_energy": 82, + "dialogflow": 623, + "songpal": 1483, + "soundtouch": 2080, + "opnsense": 322, + "lg_soundbar": 362, + "statistics": 2149, + "frontier_silicon": 1612, + "mazda": 66, + "daikin": 3030, + "emulated_hue": 2537, + "tautulli": 1287, + "syncthing": 448, + "amcrest": 1324, + "motioneye": 3538, + "directv": 312, + "rainbird": 704, + "solaredge": 4777, + "filesize": 2163, + "flume": 848, + "discord": 1478, + "sense": 1635, + "waze_travel_time": 5655, + "season": 6327, + "tod": 4741, + "verisure": 1468, + "energyzero": 715, + "hunterdouglas_powerview": 519, + "plant": 1728, + "qnap": 1871, + "metoffice": 1787, + "hdmi_cec": 67, + "sensibo": 2862, + "adax": 473, + "netgear": 2291, + "iss": 1718, + "vlc_telnet": 4141, + "kostal_plenticore": 635, + "solax": 159, + "prusalink": 744, + "econet": 761, + "coinbase": 414, + "hive": 1436, + "bmw_connected_drive": 2445, + "compensation": 171, + "nina": 2515, + "tractive": 688, + "tankerkoenig": 2261, + "nanoleaf": 3266, + "tomorrowio": 1651, + "slimproto": 338, + "squeezebox": 1398, + "schlage": 479, + "switcher_kis": 265, + "switchbot": 6961, + "switchbot_cloud": 1020, + "bayesian": 195, + "pushover": 2445, + "keenetic_ndms2": 1945, + "ourgroceries": 469, + "vicare": 1495, + "thermopro": 639, + "neato": 935, + "roon": 405, + "renault": 1287, + "bthome": 4166, + "nuki": 1974, + "modbus": 4746, + "telegram": 660, + "updater": 3559, + "aladdin_connect": 772, + "deluge": 245, + "opensky": 404, + "airzone_cloud": 120, + "ecovacs": 765, + "nws": 4082, + "alert": 2209, + "media_extractor": 1489, + "openuv": 1917, + "iqvia": 567, + "tradfri": 5548, + "velux": 291, + "growatt_server": 842, + "pvoutput": 441, + "intent": 1357, + "withings": 2143, + "soma": 95, + "ps4": 1727, + "lametric": 680, + "google_sheets": 1171, + "downloader": 1259, + "rest": 7444, + "powerwall": 1163, + "nissan_leaf": 296, + "wallbox": 885, + "glances": 2791, + "hyperion": 1487, + "dwd_weather_warnings": 2788, + "airvisual": 1363, + "dsmr_reader": 989, + "apcupsd": 871, + "sleepiq": 956, + "sabnzbd": 1023, + "steam_online": 1547, + "tellduslive": 864, + "airthings_ble": 1018, + "tellstick": 230, + "airthings": 1308, + "mill": 892, + "yalexs_ble": 953, + "environment_canada": 1702, + "homeassistant_hardware": 310, + "smtp": 233, + "thermobeacon": 614, + "eufylife_ble": 640, + "evohome": 1266, + "meater": 1302, + "notion": 134, + "fibaro": 660, + "fastdotcom": 1076, + "volvooncall": 1228, + "seventeentrack": 854, + "voip": 1332, + "lidarr": 312, + "onewire": 257, + "joaoapps_join": 177, + "zodiac": 496, + "aurora": 977, + "transmission": 1793, + "emulated_roku": 1110, + "asterisk_mbox": 26, + "rtsp_to_webrtc": 2378, + "feedreader": 729, + "google_tasks": 655, + "here_travel_time": 354, + "minecraft_server": 1106, + "gdacs": 1234, + "snmp": 345, + "gpslogger": 618, + "rachio": 3084, + "iaqualink": 328, + "kef": 51, + "fireservicerota": 34, + "gios": 280, + "mysensors": 481, + "airly": 774, + "ezviz": 2849, + "opentherm_gw": 230, + "picnic": 698, + "traccar": 556, + "bluetooth_tracker": 98, + "recollect_waste": 124, + "mqtt_room": 465, + "snapcast": 353, + "incomfort": 106, + "browser": 557, + "rpi_camera": 86, + "qbittorrent": 1307, + "nexia": 475, + "modern_forms": 340, + "fritzbox_callmonitor": 1945, + "google_travel_time": 820, + "discovery": 602, + "bosch_shc": 587, + "gree": 2372, + "waqi": 1539, + "mqtt_json": 5, + "caldav": 515, + "mqtt_statestream": 573, + "islamic_prayer_times": 523, + "todoist": 1150, + "hardkernel": 357, + "honeywell": 1736, + "ruuvitag_ble": 743, + "android_ip_webcam": 920, + "aranet": 308, + "color_extractor": 434, + "youless": 321, + "smappee": 253, + "subaru": 265, + "ukraine_alarm": 555, + "rainforest_eagle": 254, + "homematicip_cloud": 2030, + "pvpc_hourly_pricing": 1229, + "sia": 478, + "mediaroom": 36, + "iotawatt": 446, + "ipma": 636, + "trend": 207, + "uptimerobot": 933, + "huawei_lte": 646, + "mailgun": 106, + "duckdns": 1942, + "onkyo": 145, + "folder_watcher": 651, + "private_ble_device": 414, + "zwave_me": 64, + "somfy": 32, + "slack": 521, + "debugpy": 104, + "vera": 352, + "led_ble": 642, + "pushbullet": 1096, + "rflink": 835, + "weatherflow": 441, + "slide": 109, + "litterrobot": 1349, + "smart_meter_texas": 241, + "stookalert": 443, + "proxmoxve": 1348, + "motion_blinds": 628, + "lupusec": 29, + "ambiclimate": 52, + "keyboard_remote": 77, + "habitica": 94, + "zamg": 443, + "enigma2": 485, + "geo_location": 657, + "universal": 554, + "mystrom": 601, + "holiday": 1155, + "livisi": 231, + "tessie": 122, + "awair": 1025, + "microsoft_face": 1, + "zoneminder": 256, + "nibe_heatpump": 285, + "envisalink": 853, + "danfoss_air": 30, + "ihc": 257, + "nextdns": 500, + "homematic": 1187, + "lyric": 713, + "comfoconnect": 157, + "arris_tg2492lg": 2, + "nextcloud": 1136, + "hydrawise": 588, + "baf": 399, + "manual": 353, + "volumio": 1412, + "blebox": 287, + "mold_indicator": 24, + "remote_rpi_gpio": 36, + "wolflink": 179, + "modem_callerid": 24, + "rdw": 772, + "hisense_aehw4a1": 38, + "velbus": 79, + "yale_smart_alarm": 350, + "sfr_box": 174, + "luftdaten": 735, + "wiffi": 267, + "agent_dvr": 706, + "starline": 299, + "ecoforest": 10, + "nsw_fuel_station": 98, + "nzbget": 320, + "epson": 215, + "ecowitt": 3008, + "alarmdecoder": 118, + "snips": 27, + "notify_events": 423, + "jewish_calendar": 217, + "flux": 30, + "stookwijzer": 191, + "openexchangerates": 326, + "simplisafe": 804, + "trafikverket_camera": 110, + "p1_monitor": 338, + "air_quality": 386, + "totalconnect": 201, + "launch_library": 279, + "random": 180, + "no_ip": 236, + "nuheat": 166, + "tilt_ble": 84, + "satel_integra": 384, + "risco": 276, + "qnap_qsw": 52, + "kraken": 300, + "emby": 595, + "geofency": 313, + "hvv_departures": 70, + "devolo_home_control": 65, + "vulcan": 24, + "laundrify": 151, + "openhome": 730, + "rainmachine": 381, + "sms": 162, + "schluter": 92, + "sensorpro": 35, + "pegel_online": 219, + "worldclock": 30, + "rss_feed_template": 29, + "emulated_kasa": 116, + "obihai": 448, + "filter": 735, + "remember_the_milk": 35, + "tplink_omada": 1033, + "isy994": 415, + "mullvad": 121, + "rapt_ble": 32, + "eufy": 101, + "eafm": 352, + "meteoclimatic": 158, + "prosegur": 92, + "insteon": 1086, + "flipr": 67, + "intesishome": 263, + "twentemilieu": 197, + "brottsplatskartan": 170, + "kitchen_sink": 3, + "demo": 79, + "balboa": 90, + "saj": 76, + "rpi_gpio": 42, + "advantage_air": 266, + "emoncms": 15, + "synology_srm": 7, + "twilio_call": 18, + "intellifire": 34, + "opengarage": 425, + "luci": 36, + "aurora_abb_powerone": 34, + "sensorpush": 104, + "iperf3": 136, + "mutesync": 61, + "google_generative_ai_conversation": 199, + "yamaha": 135, + "solarlog": 177, + "radiotherm": 104, + "easyenergy": 140, + "plugwise": 542, + "aqualogic": 17, + "google_wifi": 43, + "dexcom": 338, + "arcam_fmj": 57, + "gogogate2": 407, + "serial": 21, + "thingspeak": 43, + "watttime": 15, + "axis": 927, + "mpd": 82, + "locative": 179, + "mopeka": 225, + "fitbit": 619, + "pilight": 10, + "anova": 80, + "control4": 137, + "electric_kiwi": 20, + "geonetnz_quakes": 157, + "amberelectric": 336, + "efergy": 286, + "coolmaster": 60, + "mitemp_bt": 24, + "omnilogic": 86, + "tmb": 10, + "trafikverket_train": 77, + "simplepush": 77, + "garmin_connect": 3, + "homeassistant_yellow": 114, + "clicksend": 7, + "flo": 412, + "supla": 109, + "netgear_lte": 64, + "forked_daapd": 293, + "safe_mode": 81, + "airzone": 141, + "somfy_mylink": 108, + "airvisual_pro": 132, + "nam": 58, + "geocaching": 328, + "electrasmart": 25, + "loqed": 211, + "twitch": 127, + "toon": 253, + "system_bridge": 214, + "recswitch": 21, + "met_eireann": 303, + "ziggo_mediabox_xl": 3, + "swiss_public_transport": 123, + "purpleair": 87, + "freedns": 50, + "venstar": 210, + "lightwave": 103, + "kaiterra": 51, + "graphite": 13, + "bluesound": 313, + "ondilo_ico": 153, + "atag": 87, + "generic_hygrostat": 763, + "screenlogic": 309, + "ws66i": 12, + "rituals_perfume_genie": 351, + "maxcube": 200, + "senseme": 9, + "elkm1": 239, + "vallox": 158, + "bsblan": 28, + "google_cloud": 19, + "darksky": 79, + "peco": 57, + "ruckus_unleashed": 97, + "splunk": 54, + "local_file": 71, + "file": 90, + "configurator": 114, + "snooz": 53, + "vodafone_station": 76, + "faa_delays": 143, + "dht": 6, + "egardia": 33, + "oncue": 73, + "tailwind": 44, + "dunehd": 102, + "flick_electric": 42, + "home_plus_control": 85, + "weishaupt_wcm_com": 1, + "sentry": 63, + "linux_battery": 6, + "nmbs": 48, + "linode": 22, + "aseko_pool_live": 7, + "senz": 90, + "whois": 365, + "ness_alarm": 40, + "google_pubsub": 23, + "worxlandroid": 14, + "goalzero": 38, + "canary": 108, + "dlink": 175, + "skybell": 68, + "moat": 4, + "dynalite": 37, + "namecheapdns": 89, + "flic": 131, + "datadog": 22, + "google_maps": 11, + "spc": 46, + "wirelesstag": 132, + "dormakaba_dkey": 8, + "microsoft": 11, + "digital_ocean": 43, + "marytts": 4, + "geniushub": 25, + "lutron": 116, + "keba": 91, + "mqtt_eventstream": 96, + "zeversolar": 88, + "fjaraskupan": 28, + "juicenet": 194, + "niko_home_control": 11, + "ads": 37, + "zabbix": 50, + "eight_sleep": 56, + "nobo_hub": 82, + "flexit_bacnet": 14, + "doods": 18, + "matrix": 213, + "mfi": 17, + "brunt": 39, + "device_sun_light_trigger": 77, + "ohmconnect": 96, + "folder": 19, + "alpha_vantage": 7, + "edimax": 14, + "lcn": 38, + "zwave": 33, + "airq": 51, + "flunearyou": 9, + "moehlenhoff_alpha2": 40, + "aftership": 60, + "thethingsnetwork": 38, + "escea": 17, + "trafikverket_ferry": 12, + "firmata": 84, + "lookin": 33, + "izone": 91, + "enocean": 219, + "geonetnz_volcano": 41, + "miflora": 15, + "airtouch4": 90, + "v2c": 32, + "ruuvi_gateway": 72, + "html5": 58, + "spider": 22, + "repetier": 35, + "idasen_desk": 125, + "melissa": 13, + "xiaomi_tv": 19, + "google_domains": 110, + "geo_json_events": 44, + "pocketcasts": 5, + "landisgyr_heat_meter": 14, + "waterfurnace": 15, + "devialet": 81, + "bluemaestro": 19, + "mochad": 25, + "mailbox": 3, + "acmeda": 29, + "anthemav": 99, + "point": 25, + "lg_netcast": 15, + "lifx_cloud": 4, + "bluetooth_le_tracker": 27, + "uvc": 46, + "ombi": 67, + "mycroft": 5, + "streamlabswater": 18, + "azure_service_bus": 2, + "reddit": 2, + "fixer": 2, + "pure_energie": 59, + "iammeter": 94, + "hlk_sw16": 14, + "foobot": 34, + "bloomsky": 6, + "smarty": 1, + "ialarm": 27, + "time": 14, + "touchline": 3, + "signal_messenger": 10, + "vlc": 7, + "ebusd": 43, + "jvc_projector": 29, + "starlingbank": 3, + "weatherkit": 95, + "openhardwaremonitor": 20, + "rympro": 21, + "gardena_bluetooth": 44, + "swiss_hydrological_data": 69, + "usgs_earthquakes_feed": 9, + "melnor": 42, + "plaato": 45, + "freedompro": 26, + "sunweg": 3, + "logi_circle": 18, + "proxy": 16, + "statsd": 4, + "baidu": 35, + "sensirion_ble": 52, + "manual_mqtt": 11, + "aws": 60, + "qvr_pro": 44, + "amazon_polly": 16, + "duotecno": 5, + "huisbaasje": 43, + "suez_water": 30, + "ridwell": 33, + "switchbee": 3, + "nightscout": 172, + "almond": 29, + "bitcoin": 25, + "smarttub": 37, + "foursquare": 7, + "aosmith": 14, + "discovergy": 91, + "w800rf32": 6, + "opple": 15, + "route53": 72, + "pioneer": 11, + "linksys_smart": 3, + "fivem": 19, + "synology_chat": 4, + "nederlandse_spoorwegen": 3, + "emoncms_history": 48, + "osramlightify": 58, + "renson": 8, + "monoprice": 114, + "ffmpeg_motion": 13, + "zestimate": 16, + "qwikswitch": 9, + "meteoalarm": 8, + "denon": 6, + "kaleidescape": 20, + "tomato": 7, + "azure_event_hub": 20, + "ozw": 7, + "panasonic_bluray": 3, + "keymitt_ble": 9, + "tcp": 10, + "kmtronic": 21, + "syslog": 8, + "picotts": 21, + "twilio_sms": 29, + "upb": 32, + "hp_ilo": 12, + "minio": 7, + "quantum_gateway": 5, + "itunes": 7, + "nsw_rural_fire_service_feed": 2, + "transport_nsw": 4, + "x10": 2, + "familyhub": 13, + "switchmate": 37, + "harman_kardon_avr": 3, + "pjlink": 6, + "kegtron": 5, + "azure_devops": 23, + "beewi_smartclim": 1, + "poolsense": 22, + "meraki": 4, + "dte_energy_bridge": 2, + "apache_kafka": 1, + "todo": 5, + "torque": 5, + "vilfo": 2, + "itach": 3, + "msteams": 13, + "sisyphus": 22, + "hikvisioncam": 18, + "tami4": 16, + "london_underground": 3, + "sendgrid": 4, + "emonitor": 5, + "free_mobile": 9, + "limitlessled": 22, + "entur_public_transport": 7, + "linear_garage_door": 2, + "event": 4, + "voicerss": 3, + "comapsmarthome": 2, + "yardian": 13, + "plum_lightpad": 1, + "xiaomi": 14, + "bt_smarthub": 4, + "imap_email_content": 14, + "prowl": 9, + "russound_rio": 3, + "serial_pm": 2, + "raspyrfm": 1, + "oru": 4, + "sesame": 5, + "watson_tts": 7, + "sky_hub": 2, + "tolo": 3, + "ffmpeg_noise": 9, + "kira": 8, + "aprs": 5, + "otp": 2, + "gtfs": 2, + "stiebel_eltron": 19, + "osoenergy": 10, + "orvibo": 14, + "homeworks": 8, + "lannouncer": 7, + "guardian": 9, + "rmvtransport": 2, + "greeneye_monitor": 6, + "openevse": 1, + "nextbus": 16, + "clickatell": 2, + "gc100": 4, + "facebook": 3, + "progettihwsw": 2, + "warnwetter": 2, + "unifiled": 1, + "telnet": 9, + "ted5000": 2, + "dremel_3d_printer": 11, + "neurio_energy": 1, + "ddwrt": 7, + "climacell": 2, + "lirc": 3, + "refoss": 2, + "nad": 2, + "raincloud": 3, + "aquostv": 4, + "auth_header": 3, + "sharpai": 3, + "channels": 3, + "yandex_transport": 2, + "mastodon": 1, + "comelit": 3, + "sighthound": 2, + "eq3btsmart": 5, + "message_bird": 4, + "pyload": 1, + "citybikes": 2, + "comed_hourly_pricing": 4, + "xmpp": 3, + "worldtidesinfo": 2, + "ccm15": 2, + "netdata": 3, + "fritzbox_netmonitor": 4, + "apprise": 2, + "drop_connect": 1, + "permobil": 3, + "norway_air": 3, + "push": 2, + "upc_connect": 2, + "ecoal_boiler": 3, + "garages_amsterdam": 10, + "elmax": 6, + "unifi_direct": 5, + "tesla": 6, + "atome": 2, + "london_air": 1, + "anel_pwrctrl": 5, + "pushsafer": 2, + "supervisord": 2, + "hangouts": 4, + "crownstone": 6, + "noaa_tides": 6, + "delijn": 1, + "numato": 1, + "evil_genius_labs": 4, + "temper": 4, + "ign_sismologia": 2, + "yi": 3, + "xs1": 4, + "datetime": 2, + "opensensemap": 4, + "sinch": 2, + "logentries": 1, + "dominos": 2, + "shodan": 1, + "ephember": 3, + "mcp23017": 1, + "currencylayer": 2, + "music_light_switch": 1, + "weather_light_switch": 1, + "fortios": 1, + "clicksend_tts": 1, + "deutsche_bahn": 3, + "geo_rss_events": 2, + "rejseplanen": 4, + "wilight": 3, + "lacrosse": 3, + "aruba": 1, + "yeelightsunflower": 2, + "hddtemp": 2, + "volkszaehler": 1, + "llamalab_automate": 1, + "viaggiatreno": 1, + "valve": 1, + "smartthings_soundbar": 1, + "samsungtv_smart": 1, + "gardena_smart_system": 1, + "sonoff": 1, + "sony_projector": 3, + "facebox": 1, + "bbox": 2, + "twitter": 4, + "asustor": 1, + "vivotek": 1, + "mythicbeastsdns": 2, + "dweet": 1, + "vasttrafik": 1, + "uk_transport": 4, + "scsgate": 1, + "veea": 1, + "dovado": 1, + "garadget": 2, + "simulated": 1, + "gpsd": 1, + "wsdot": 1, + "swisscom": 1, + "bme280": 1, + "spaceapi": 3, + "nx584": 1, + "discogs": 1, + "fail2ban": 1, + "futurenow": 1, + "idteck_prox": 1, + "seven_segments": 3, + "qld_bushfire": 1, + "cups": 2, + "arest": 1, + "eliqonline": 1, + "yandextts": 2, + "blockchain": 1, + "date": 1, + "elv": 1, + "tank_utility": 1, + "etherscan": 1, + "blue_current": 1, + "tplink_lte": 1, + "rpi_rf": 1 + }, + "operating_system": { + "boards": { + "ova": 62036, + "rpi3-64": 17386, + "rpi4-64": 61880, + "rpi4": 1476, + "generic-x86-64": 27962, + "yellow": 4767, + "odroid-xu4": 191, + "green": 3671, + "rpi3": 2016, + "odroid-n2": 4919, + "rpi2": 516, + "odroid-c2": 122, + "generic-aarch64": 1088, + "odroid-m1": 195, + "tinker": 111, + "rpi5-64": 686, + "khadas-vim3": 53, + "odroid-c4": 227 + }, + "versions": { + "10.5": 9182, + "11.3": 50875, + "11.2": 53976, + "10.2": 1131, + "11.4": 26448, + "11.1": 25489, + "10.4": 2017, + "9.3": 991, + "10.3": 3390, + "11.0": 3494, + "9.5": 3518, + "11.4.rc1": 313, + "11.3.rc1": 405, + "9.4": 1314, + "10.0": 337, + "11.0.rc1": 12, + "9.0": 293, + "10.1": 1631, + "8.4": 258, + "7.0": 124, + "9.2": 255, + "8.5": 629, + "11.2.rc1": 86, + "8.2": 373, + "7.6": 540, + "6.4": 110, + "7.1": 117, + "6.6": 253, + "6.3": 35, + "5.13": 85, + "6.2": 97, + "6.0": 31, + "11.3.dev20231221": 39, + "8.3": 11, + "7.4": 264, + "11.4.dev20231223": 45, + "11.3.dev20231212": 41, + "11.4.dev20240107": 13, + "8.1": 180, + "11.3.dev20231214": 47, + "7.3": 27, + "11.0.dev20230926": 3, + "6.5": 64, + "11.2.rc2": 83, + "11.3.rc2": 119, + "8.0": 32, + "6.1": 67, + "7.2": 96, + "11.4.dev20240108": 44, + "8.0.rc3": 1, + "11.2.dev20231120": 3, + "11.4.dev20231226": 87, + "10.0.rc3": 10, + "7.5": 99, + "9.0.rc1": 3, + "11.3.dev20231220": 10, + "11.4.dev20240105": 14, + "8.0.dev20220126": 1, + "7.0.rc1": 3, + "8.0.rc2": 4, + "10.0.rc2": 4, + "11.2.dev20231116": 3, + "11.1.rc1": 8, + "10.0.dev20220912": 1, + "5.11": 2, + "11.3.dev20231201": 12, + "11.0.dev20230511": 2, + "11.0.dev20230609": 1, + "4.20": 2, + "11.0.dev20230921": 2, + "11.2.dev20231102": 2, + "11.0.dev20230627": 1, + "11.3.dev20231211": 3, + "5.12": 7, + "11.0.dev20231004": 1, + "10.0.dev20221130": 1, + "11.2.dev20231109": 3, + "9.0.rc2": 6, + "11.0.dev20230705": 1, + "11.0.dev20230524": 1, + "11.0.dev20230601": 1, + "5.10": 1, + "11.2.dev20231106": 1, + "8.0.dev20220505": 1, + "11.0.rc2": 2, + "11.0.dev20230720": 1, + "4.12": 1, + "11.0.dev20230622": 1, + "9.1": 1, + "10.0.dev20221110": 1, + "11.3.dev0": 1, + "11.0.dev20230416": 1, + "8.0.rc4": 1, + "10.0.rc1": 1, + "11.0.dev20230413": 1, + "10.0.dev20221222": 1, + "11.2.dev20231031": 1, + "11.1.dev20231011": 1, + "11.0.dev20230823": 1 + } + }, + "supervisor": { + "arch": { + "amd64": 96508, + "aarch64": 100709, + "armv7": 4920, + "armhf": 375, + "i386": 20 + }, + "unhealthy": 3486, + "unsupported": 9329 + }, + "reports_integrations": 249256, + "reports_addons": 174735, + "reports_statistics": 241738, + "versions": { + "2023.5.4": 1615, + "2024.1.2": 97867, + "2023.9.1": 1000, + "2023.12.4": 29374, + "2022.8.6": 238, + "2023.11.3": 23970, + "2023.5.3": 979, + "2024.1.1": 6877, + "2023.11.1": 3990, + "2023.6.2": 1001, + "2023.12.3": 28121, + "2023.10.0": 726, + "2023.8.4": 2548, + "2023.12.1": 10408, + "2023.2.5": 1220, + "2023.1.7": 1346, + "2024.1.0": 12599, + "2023.10.1": 2179, + "2022.12.8": 904, + "2023.3.6": 1220, + "2023.8.0": 667, + "2023.7.0": 161, + "2022.12.6": 202, + "2023.3.3": 502, + "2023.10.4": 300, + "2024.2.0.dev20240105": 8, + "2023.6.1": 779, + "2023.11.2": 15694, + "2022.11.3": 216, + "2023.10.5": 5379, + "2023.12.0": 4192, + "2023.9.2": 3728, + "2023.7.3": 3932, + "2023.6.3": 2790, + "2023.4.2": 399, + "2023.12.2": 2328, + "2022.4.7": 184, + "2023.10.3": 4071, + "2023.9.3": 3649, + "2023.2.1": 189, + "2023.8.2": 1219, + "2023.3.1": 516, + "2023.6.0": 167, + "2021.4.3": 39, + "2022.10.4": 250, + "2023.3.5": 524, + "2023.5.2": 947, + "2023.4.5": 357, + "2023.8.3": 1211, + "2022.10.5": 677, + "2021.4.6": 89, + "2023.4.6": 1336, + "2023.3.4": 243, + "2022.12.9": 142, + "2022.3.5": 148, + "2023.4.4": 429, + "2022.8.7": 383, + "2021.12.3": 76, + "2023.1.0.dev20221210": 1, + "2023.8.1": 1298, + "2023.4.1": 271, + "2024.1.0.dev20231223": 14, + "2022.6.0.dev20220502": 1, + "2021.12.9": 156, + "2022.8.4": 80, + "2022.11.5": 434, + "2021.11.5": 397, + "2023.11.0": 1914, + "2024.1.0b2": 68, + "2023.9.0": 502, + "2023.1.6": 224, + "2023.1.2": 265, + "2022.4.5": 75, + "2022.11.2": 449, + "2021.6.6": 1306, + "2023.7.1": 1090, + "2022.11.4": 559, + "2021.11.1": 67, + "2022.4.0": 34, + "2023.2.3": 461, + "2023.7.2": 1224, + "2023.2.2": 243, + "2022.8.5": 53, + "2021.12.8": 436, + "2023.1.0": 110, + "2022.2.9": 273, + "2021.8.8": 174, + "2022.9.4": 162, + "2022.10.1": 117, + "2023.7.0.dev20230626": 233, + "2023.1.1": 271, + "2022.7.6": 111, + "2022.12.3": 73, + "2022.12.0": 101, + "2022.8.2": 62, + "2021.12.7": 131, + "2022.7.2": 56, + "2021.8.5": 7, + "2022.9.7": 344, + "2022.5.2": 34, + "2022.10.0": 50, + "2021.9.7": 433, + "2022.7.3": 69, + "2021.12.6": 34, + "2021.12.2": 40, + "2024.1.0b1": 9, + "2021.12.10": 429, + "2023.1.4": 319, + "2021.4.4": 27, + "2022.9.6": 191, + "2022.6.7": 379, + "2022.7.7": 189, + "2022.5.1": 21, + "2022.5.4": 132, + "2022.9.0": 59, + "2022.5.3": 87, + "2022.11.1": 255, + "2022.2.5": 45, + "2021.10.6": 163, + "2022.3.8": 164, + "2024.1.0b0": 26, + "2022.7.5": 158, + "2022.3.7": 106, + "2022.8.1": 63, + "2021.8.4": 20, + "2022.8.0": 37, + "2021.12.5": 89, + "2023.3.0": 83, + "2022.9.2": 67, + "2024.1.0.dev20231222": 7, + "2021.6.4": 22, + "2023.5.0.dev20230413": 1, + "2021.5.1": 23, + "2022.9.5": 87, + "2022.6.3": 28, + "2021.9.4": 17, + "2022.12.1": 191, + "2023.2.0b9": 2, + "2022.12.7": 228, + "2023.10.2": 316, + "2022.4.4": 39, + "2022.3.6": 29, + "2023.12.0.dev20231122": 5, + "2022.5.5": 250, + "2023.2.0": 116, + "2022.2.0": 25, + "2021.9.5": 23, + "2023.2.4": 165, + "2022.10.3": 154, + "2022.4.3": 29, + "2021.10.7": 35, + "2023.4.3": 54, + "2022.12.4": 40, + "2021.10.5": 23, + "2023.5.0": 99, + "2022.3.1": 68, + "2022.6.6": 175, + "2022.2.2": 49, + "2023.8.0.dev20230723": 123, + "2021.8.7": 28, + "2022.7.4": 33, + "2023.5.1": 91, + "2024.1.0.dev20231212": 15, + "2024.2.0.dev20240109": 23, + "2024.1.0.dev20231215": 7, + "2023.11.0.dev20231019": 1, + "2022.2.7": 16, + "2023.6.0.dev20230528": 1, + "2023.1.0b5": 1, + "2021.6.5": 52, + "2021.11.4": 57, + "2021.5.5": 78, + "2021.11.0": 33, + "2022.2.1": 13, + "2023.3.2": 98, + "2021.8.6": 35, + "2022.9.1": 99, + "2023.9.0.dev20230830": 1, + "2022.2.8": 44, + "2023.12.0.dev20231121": 2, + "2023.1.5": 217, + "2021.10.2": 30, + "2023.5.0.dev20230408": 1, + "2022.10.2": 57, + "2021.10.0.dev20210916": 1, + "2022.6.5": 106, + "2022.3.0": 41, + "2023.4.0": 122, + "2022.11.0b0": 1, + "2021.5.2": 13, + "2023.10.0.dev20230910": 1, + "2023.3.0b4": 1, + "2022.6.4": 73, + "2021.9.1": 6, + "2021.9.6": 82, + "2023.10.0b4": 3, + "2024.1.0.dev20231218": 99, + "2021.7.4": 106, + "2024.2.0.dev20240101": 2, + "2023.12.0b1": 46, + "2022.6.2": 59, + "2022.2.3": 76, + "2021.11.3": 53, + "2024.2.0.dev20240110": 22, + "2024.1.0b3": 54, + "2022.12.2": 23, + "2024.2.0.dev20240107": 10, + "2022.8.3": 69, + "2022.7.0": 40, + "2023.10.0.dev20230916": 1, + "2022.3.3": 103, + "2022.12.5": 56, + "2022.4.6": 90, + "2021.9.3": 18, + "2023.9.0b3": 6, + "2024.2.0.dev20240108": 14, + "2022.2.6": 98, + "2021.12.4": 49, + "2022.4.1": 62, + "2021.5.0": 11, + "2022.6.0": 31, + "2023.4.0.dev20230304": 1, + "2022.6.0b1": 1, + "2021.10.4": 32, + "2021.6.0.dev20210512": 1, + "2024.1.0b5": 32, + "2022.11.0": 40, + "2022.7.0.dev20220618": 1, + "2021.6.3": 35, + "2024.1.0b4": 27, + "2021.5.4": 34, + "2021.7.1": 36, + "2024.1.0.dev20231211": 4, + "2021.12.1": 36, + "2023.4.0.dev20230307": 3, + "2024.1.0b7": 13, + "2024.1.0b8": 15, + "2021.4.5": 33, + "2022.6.1": 47, + "2023.10.0.dev20230927": 2, + "2024.1.0.dev20231220": 6, + "2022.7.0.dev20220526": 1, + "2022.10.0.dev20220918": 1, + "2024.1.0.dev20231227": 7, + "2021.8.0": 9, + "2023.9.0b4": 1, + "2021.7.0": 4, + "2021.7.3": 46, + "2022.3.4": 30, + "2023.10.0.dev20230917": 1, + "2023.11.0b0": 3, + "2024.1.0.dev20231209": 9, + "2021.8.3": 12, + "2022.11.0.dev20221002": 1, + "2023.5.0.dev20230403": 1, + "2022.2.0.dev20211217": 1, + "2022.7.1": 16, + "2021.6.2": 28, + "2024.1.0.dev20231221": 15, + "2023.2.0.dev20230102": 4, + "2022.12.0.dev20221119": 1, + "2023.5.0.dev20230418": 1, + "2023.4.0.dev20230324": 1, + "2021.10.3": 11, + "2022.4.2": 23, + "2022.3.2": 36, + "2022.6.0.dev20220515": 1, + "2022.5.0": 37, + "2023.12.0.dev20231102": 2, + "2023.1.3": 6, + "2024.2.0.dev20231231": 2, + "2024.2.0.dev20240106": 6, + "2021.9.2": 10, + "2021.5.3": 19, + "2021.12.0": 29, + "2022.9.0.dev20220827": 2, + "2023.6.0.dev20230515": 1, + "2021.12.0b5": 1, + "2024.2.0.dev20231229": 5, + "2022.4.0.dev20220327": 1, + "2022.8.0.dev20220711": 1, + "2024.1.0.dev20231216": 7, + "2022.3.0.dev20220205": 2, + "2022.3.0b4": 1, + "2024.1.0.dev20231217": 9, + "2023.11.0b4": 2, + "2023.4.0.dev20230227": 1, + "2023.5.0b9": 1, + "2023.2.0b6": 1, + "2022.12.0.dev20221120": 1, + "2022.11.0b7": 1, + "2021.6.0": 4, + "2024.1.0.dev20231204": 1, + "2021.11.2": 29, + "2021.4.1": 6, + "2023.3.0.dev20230205": 1, + "2023.9.0b5": 2, + "2021.7.2": 23, + "2023.1.0.dev20221218": 1, + "2023.3.0.dev20230210": 3, + "2023.12.0b0": 13, + "2023.12.0b3": 5, + "2023.1.0.dev20221220": 1, + "2023.12.0.dev20231128": 5, + "2022.2.0.dev20220121": 1, + "2023.7.0.dev20230604": 1, + "2022.3.0.dev20220214": 2, + "2022.6.0.dev20220525": 2, + "2021.4.0": 3, + "2023.12.0.dev20231110": 1, + "2023.11.0.dev20231012": 4, + "2023.5.0.dev20230410": 1, + "2023.12.0b5": 5, + "2022.7.0.dev20220623": 1, + "2024.2.0.dev20231228": 3, + "2024.2.0.dev20231230": 8, + "2023.12.0.dev20231127": 1, + "2023.5.0.dev20230423": 1, + "2022.11.0.dev20221009": 1, + "2022.12.0.dev20221105": 5, + "2022.8.0.dev20220704": 1, + "2023.2.0.dev20230116": 1, + "2022.10.0b5": 1, + "2022.4.0.dev20220225": 1, + "2023.3.0.dev20230216": 1, + "2022.12.0.dev20221029": 1, + "2023.6.0.dev20230530": 2, + "2024.1.0.dev20231226": 9, + "2023.9.0.dev20230810": 2, + "2023.10.0b0": 3, + "2023.9.0b0": 2, + "2023.9.0b2": 1, + "2021.9.0.dev20210824": 1, + "2024.2.0.dev20240102": 19, + "2023.9.0.dev20230807": 2, + "2023.11.0b3": 2, + "2021.11.0.dev20211007": 1, + "2023.6.0.dev20230512": 1, + "2023.12.0.dev20231123": 2, + "2023.8.0.dev20230721": 1, + "2021.9.0": 7, + "2023.5.0b6": 1, + "2023.10.0b2": 4, + "2023.10.0.dev20230920": 1, + "2023.4.0.dev20230223": 1, + "2023.11.0b2": 6, + "2023.12.0.dev20231116": 1, + "2023.5.0.dev20230416": 1, + "2023.10.0b9": 3, + "2022.8.0b7": 1, + "2022.8.0b3": 1, + "2022.10.0.dev20220903": 2, + "2023.12.0b2": 13, + "2021.10.0": 21, + "2023.6.0.dev20230520": 1, + "2022.9.3": 8, + "2023.4.0.dev20230320": 3, + "2022.5.0.dev20220423": 1, + "2023.3.0.dev20230201": 1, + "2023.7.0.dev20230616": 1, + "2022.2.0.dev20220103": 1, + "2023.10.0b3": 3, + "2023.2.0.dev20221230": 1, + "2021.10.1": 7, + "2022.9.0.dev20220823": 1, + "2023.6.0b1": 1, + "2022.6.0.dev20220504": 1, + "2021.8.2": 7, + "2024.1.0.dev20231225": 5, + "2023.9.0.dev20230728": 1, + "2023.12.0.dev20231118": 2, + "2022.6.0.dev20220429": 1, + "2023.8.0.dev20230701": 1, + "2023.12.0.dev20231103": 1, + "2023.4.0.dev20230318": 1, + "2021.11.0.dev20211020": 1, + "2023.1.0.dev20221222": 2, + "2023.8.0b1": 2, + "2023.5.0.dev20230412": 2, + "2022.2.0.dev20220108": 1, + "2021.9.0.dev20210823": 1, + "2024.1.0.dev20231213": 9, + "2023.4.0b6": 1, + "2021.8.1": 7, + "2024.1.0.dev20231201": 3, + "2023.7.0b2": 1, + "2023.5.0.dev20230331": 1, + "2023.11.0.dev20231017": 1, + "2023.12.0.dev20231111": 1, + "2024.1.0.dev20231130": 1, + "2023.7.0.dev20230606": 1, + "2023.12.0.dev20231119": 5, + "2024.1.0.dev20231219": 6, + "2022.1.0.dev20211212": 1, + "2023.12.0.dev20231107": 1, + "2022.2.0.dev20211225": 1, + "2023.4.0.dev20230312": 1, + "2023.12.0.dev20231126": 6, + "2022.9.0b6": 2, + "2023.4.0.dev20230326": 1, + "2023.3.0.dev20230203": 1, + "2024.1.0.dev20231205": 2, + "2023.5.0.dev20230411": 1, + "2023.12.0b4": 6, + "2022.5.0.dev20220410": 3, + "2022.9.0.dev20220901": 3, + "2023.4.0b5": 2, + "2023.6.0b0": 1, + "2024.1.0.dev20231208": 3, + "2023.2.0b2": 2, + "2023.1.0.dev20221217": 1, + "2022.12.0.dev20221123": 1, + "2023.5.0.dev20230406": 2, + "2022.5.0.dev20220411": 1, + "2022.12.0.dev20221112": 1, + "2023.1.0.dev20221228": 2, + "2023.5.0.dev20230417": 1, + "2023.5.0.dev20230401": 2, + "2023.6.0.dev20230428": 1, + "2023.1.0b3": 1, + "2022.2.0b6": 1, + "2023.4.0.dev20230305": 1, + "2021.7.0.dev20210603": 1, + "2023.12.0.dev20231125": 2, + "2021.5.0.dev20210422": 1, + "2021.12.0b1": 1, + "2023.3.0b3": 3, + "2023.6.0.dev20230502": 1, + "2023.11.0.dev20231008": 1, + "2021.10.0b5": 1, + "2023.12.0.dev20231029": 2, + "2023.6.0.dev20230511": 1, + "2023.6.0.dev20230519": 1, + "2023.6.0b4": 2, + "2023.2.0.dev20230120": 2, + "2021.9.0b3": 1, + "2023.9.0.dev20230805": 1, + "2022.11.0b6": 3, + "2023.6.0.dev20230527": 1, + "2022.8.0.dev20220721": 1, + "2023.6.0.dev20230523": 1, + "2022.12.0.dev20221125": 1, + "2022.3.0.dev20220220": 1, + "2023.5.0.dev20230424": 1, + "2022.11.0b4": 1, + "2023.3.0.dev20230222": 1, + "2023.11.0b1": 1, + "2023.6.0.dev20230508": 1, + "2023.9.0.dev20230812": 1, + "2022.7.0b5": 1, + "2023.4.0.dev20230329": 1, + "2023.11.0b5": 3, + "2023.1.0b0": 1, + "2022.10.0b1": 1, + "2022.12.0b4": 1, + "2023.1.0.dev20221204": 1, + "2023.7.0b3": 2, + "2021.6.1": 2, + "2023.1.0.dev20221221": 1, + "2022.11.0b3": 1, + "2023.3.0.dev20230130": 1, + "2021.10.0.dev20210911": 1, + "2023.9.0.dev20230821": 1, + "2022.10.0b6": 1, + "2021.9.0.dev20210814": 1, + "2024.1.0.dev20231207": 2, + "2023.12.0.dev20231106": 1, + "2022.8.0.dev20220630": 1, + "2021.4.2": 4, + "2023.12.0.dev20231113": 1, + "2022.3.0.dev20220212": 1, + "2023.8.0b4": 2, + "2023.7.0.dev20230624": 1, + "2022.9.0.dev20220731": 1, + "2023.1.0.dev20221201": 1, + "2023.4.0.dev20230303": 1, + "2022.10.0.dev20220928": 1, + "2023.12.0.dev20231108": 1, + "2021.9.0b7": 1, + "2022.12.0.dev20221110": 1, + "2022.6.0.dev20220509": 1, + "2022.4.0.dev20220323": 1, + "2023.5.0.dev20230414": 3, + "2022.2.0.dev20211228": 1, + "2023.3.0b7": 1, + "2023.4.0.dev20230310": 1, + "2023.8.0.dev20230725": 1, + "2023.4.0.dev20230224": 1, + "2024.1.0.dev20231210": 1, + "2022.10.0.dev20220925": 1, + "2022.12.0.dev20221115": 1, + "2022.6.0.dev20220510": 1, + "2023.4.0.dev20230325": 1, + "2023.6.0.dev20230509": 1, + "2023.6.0.dev20230501": 1, + "2023.7.0.dev20230623": 1, + "2022.12.0.dev20221111": 1, + "2023.10.0.dev20230922": 1, + "2021.5.0.dev20210410": 1, + "2023.12.0.dev20231114": 1, + "2023.9.0.dev20230727": 1, + "2023.12.0.dev20231120": 1, + "2023.10.0.dev20230904": 1, + "2022.7.0.dev20220621": 1, + "2023.7.0.dev20230611": 1, + "2023.6.0b3": 1, + "2023.10.0b6": 1, + "2023.4.0.dev20230311": 1, + "2023.11.0b6": 1, + "2022.12.0b2": 1, + "2023.3.0.dev20230202": 1, + "2023.10.0.dev20230918": 1, + "2023.11.0.dev20231009": 1, + "2022.2.0.dev20220123": 1, + "2023.6.0.dev20230526": 1, + "2023.4.0.dev20230313": 1, + "2023.12.0.dev20231124": 1, + "2023.6.0b6": 1, + "2023.2.0.dev20230124": 1, + "2023.4.0.dev20230309": 1, + "2023.8.0.dev20230712": 2, + "2022.8.0.dev20220725": 1, + "2023.3.0.dev20230127": 1, + "2022.7.0b3": 1, + "2022.5.0.dev20220406": 1, + "2023.9.0.dev20230826": 1, + "2023.2.0.dev20230115": 1, + "2023.12.0.dev20231115": 1, + "2022.5.0b3": 1, + "2023.2.0b1": 1, + "2022.4.0.dev20220330": 1, + "2023.2.0.dev20230121": 2, + "2022.12.0.dev20221108": 1, + "2023.11.0.dev20231011": 1, + "2022.9.0b5": 1, + "2023.11.0.dev20231007": 1, + "2024.1.0.dev20231203": 2, + "2024.1.0b6": 2, + "2023.5.0.dev20230402": 1, + "2023.5.0.dev20230426": 2, + "2023.6.0.dev20230507": 1, + "2021.8.0.dev20210707": 1, + "2022.7.0.dev20220616": 1, + "2022.11.0b5": 1, + "2023.1.0.dev20221208": 1, + "2021.5.0.dev20210427": 1, + "2022.7.0.dev20220603": 1, + "2023.6.0.dev20230522": 1, + "2023.4.0.dev20230327": 1, + "2023.11.0.dev20231001": 1, + "2021.8.0.dev20210716": 1, + "2023.12.0.dev20231101": 1, + "2023.6.0.dev20230524": 1, + "2023.5.0.dev20230421": 1, + "2022.12.0b7": 1 + }, + "certificate_count_configured": 19511, + "energy": { + "count_configured": 90827 + }, + "recorder": { + "engines": { + "sqlite": { + "versions": { + "3.41.2": 198778, + "3.40.1": 3763, + "3.38.5": 4503, + "3.42.0": 2753, + "3.39.3": 187, + "3.37.2": 666, + "3.31.1": 864, + "3.43.1": 47, + "3.39.2": 13, + "3.43.0": 19, + "3.34.1": 284, + "3.44.2": 251, + "3.36.0": 30, + "3.39.4": 42, + "3.43.2": 56, + "3.39.5": 2, + "3.44.0": 87, + "3.44.1": 4, + "3.32.3": 2, + "3.41.1": 2, + "3.38.2": 1, + "3.37.0": 2, + "3.41.0": 5, + "3.40.0": 3, + "3.35.5": 3, + "3.37.1": 1, + "3.39.0": 3 + }, + "count_configured": 212371 + }, + "mysql": { + "versions": { + "10.6.12-MariaDB": 13673, + "10.6.12-MariaDB-0ubuntu0.22.04.1": 124, + "10.11.4-MariaDB-1~deb12u1": 91, + "8.0.31": 7, + "11.1.2-MariaDB-1:11.1.2+maria~ubu2204": 131, + "10.6.11-MariaDB-1:10.6.11+maria~ras11": 1, + "11.1.3-MariaDB-1:11.1.3+maria~deb12": 43, + "11.2.2-MariaDB-1:11.2.2+maria~ubu2204": 621, + "10.11.2-MariaDB-log": 5, + "10.11.5-MariaDB": 102, + "8.0.32": 35, + "10.7.8-MariaDB-1:10.7.8+maria~ubu2004": 32, + "10.6.16-MariaDB-log": 1, + "10.7.7-MariaDB": 1, + "11.0.3-MariaDB-1:11.0.3+maria~ubu2204": 13, + "10.10.2-MariaDB-1:10.10.2+maria~deb10": 3, + "11.0.3-MariaDB-1:11.0.3+maria~deb11": 7, + "10.11.2-MariaDB-1": 10, + "11.2.2-MariaDB": 23, + "10.5.21-MariaDB-0+deb11u1-log": 6, + "10.6.11-MariaDB-log": 22, + "10.11.2-MariaDB-1:10.11.2+maria~ubu2204": 97, + "10.5.19-MariaDB-1:10.5.19+maria~deb11": 1, + "11.1.2-MariaDB-1:11.1.2+maria~deb12": 62, + "10.10.2-MariaDB-1:10.10.2+maria~ubu2204": 57, + "10.6.10-MariaDB": 133, + "10.5.21-MariaDB-0+deb11u1": 100, + "10.11.4-MariaDB-1:10.11.4+maria~ubu2204": 8, + "10.6.16-MariaDB-cll-lve-log": 1, + "8.0.17": 2, + "10.10.2-MariaDB-1:10.10.2+maria~deb11": 6, + "10.5.8-MariaDB-log": 25, + "8.0.23-0ubuntu0.20.04.1": 1, + "10.11.5-MariaDB-log": 297, + "10.3.32-MariaDB": 95, + "8.0.35": 26, + "10.10.7-MariaDB-1:10.10.7+maria~deb11": 20, + "10.9.5-MariaDB": 1, + "11.0.2-MariaDB-1:11.0.2+maria~ubu2204": 50, + "10.6.14-MariaDB-1:10.6.14+maria~ubu2004": 5, + "10.6.12-MariaDB-log": 56, + "10.9.8-MariaDB-1:10.9.8+maria~deb11": 14, + "10.5.23-MariaDB-1:10.5.23+maria~ubu2004": 19, + "8.1.0": 15, + "11.2.2-MariaDB-1:11.2.2+maria~deb12": 112, + "8.2.0": 76, + "10.11.2-MariaDB": 167, + "10.9.3-MariaDB-1:10.9.3+maria~ubu2204": 13, + "10.5.19-MariaDB-1:10.5.19+maria~ubu2004": 10, + "10.8.8-MariaDB-1:10.8.8+maria~deb11": 8, + "10.11.3-MariaDB-1:10.11.3+maria~ubu2204": 26, + "10.11.3-MariaDB-1+rpi1": 11, + "10.6.12-MariaDB-1:10.6.12+maria~deb11": 2, + "10.3.29-MariaDB": 45, + "10.11.4-MariaDB": 10, + "8.0.35-0ubuntu0.22.04.1": 57, + "10.6.12-MariaDB-1:10.6.12+maria~deb10": 2, + "10.11.3-MariaDB-1": 23, + "10.4.19-MariaDB": 5, + "10.10.7-MariaDB-1:10.10.7+maria~ubu2204": 11, + "10.10.3-MariaDB-1:10.10.3+maria~ubu2204": 31, + "10.11.6-MariaDB": 7, + "10.5.18-MariaDB-log": 2, + "10.11.2-MariaDB-1:10.11.2+maria~deb11": 15, + "8.0.35-27": 4, + "10.5.11-MariaDB": 2, + "11.1.3-MariaDB-1:11.1.3+maria~ubu2204": 29, + "10.7.8-MariaDB-1:10.7.8+maria~deb11": 11, + "10.5.19-MariaDB": 4, + "10.5.23-MariaDB": 9, + "10.6.16-MariaDB-1:10.6.16+maria~ubu2004-log": 3, + "10.10.5-MariaDB-1:10.10.5+maria~deb11": 1, + "8.0.35-0ubuntu0.20.04.1": 17, + "10.10.3-MariaDB-1:10.10.3+maria~deb11": 8, + "10.6.12-MariaDB-1:10.6.12+maria~ubu2004": 8, + "10.5.17-MariaDB-log": 15, + "10.9.3-MariaDB-1:10.9.3+maria~deb11": 6, + "10.8.8-MariaDB": 3, + "10.6.12-MariaDB-0ubuntu0.22.04.1-log": 23, + "10.11.3-MariaDB": 2, + "10.9.3-MariaDB": 5, + "10.9.4-MariaDB-1:10.9.4+maria~ubu2204": 7, + "10.9.2-MariaDB-1:10.9.2+maria~ubu2204": 6, + "8.0.34-0ubuntu0.22.04.1": 3, + "10.6.14-MariaDB": 9, + "10.5.18-MariaDB-1:10.5.18+maria~ubu2004": 7, + "11.1.2-MariaDB": 7, + "10.6.16-MariaDB-1:10.6.16+maria~ubu2004": 23, + "10.5.18-MariaDB-0+deb11u1": 21, + "10.6.13-MariaDB-1:10.6.13+maria~ubu1804": 1, + "10.11.4-MariaDB-log": 40, + "10.8.8-MariaDB-1:10.8.8+maria~ubu2204": 10, + "10.3.38-MariaDB-0+deb10u1": 3, + "10.10.2-MariaDB": 2, + "10.4.8-MariaDB-1:10.4.8+maria~bionic": 1, + "10.11.5-MariaDB-1:10.11.5+maria~ubu2204": 14, + "10.5.18-MariaDB": 5, + "8.0.27": 15, + "10.3.12-MariaDB-1:10.3.12+maria~bionic": 1, + "10.7.5-MariaDB-1:10.7.5+maria~ubu2004": 7, + "10.11.6-MariaDB-1:10.11.6+maria~ubu2204": 48, + "10.11.3-MariaDB-log": 2, + "8.0.26": 2, + "10.5.22-MariaDB": 7, + "10.6.16-MariaDB-1:10.6.16+maria~deb11": 4, + "11.2.2-MariaDB-1:11.2.2+maria~ubu2204-log": 12, + "8.0.19": 2, + "10.10.7-MariaDB-1:10.10.7+maria~deb10": 1, + "10.6.11-MariaDB": 13, + "10.9.4-MariaDB": 2, + "11.1.2-MariaDB-1:11.1.2+maria~ubu2204-log": 1, + "11.2.2-MariaDB-1:11.2.2+maria~deb11": 8, + "8.0.28": 4, + "8.0.29": 15, + "11.2.2-MariaDB-log": 4, + "10.8.7-MariaDB-1:10.8.7+maria~ubu2204": 2, + "8.0.30": 12, + "10.7.8-MariaDB": 2, + "10.10.3-MariaDB": 4, + "11.1.3-MariaDB-log": 1, + "8.0.34-26": 6, + "10.5.19-MariaDB-0+deb11u2": 34, + "10.8.2-MariaDB-1:10.8.2+maria~focal": 2, + "10.11.6-MariaDB-1:10.11.6+maria~deb11": 17, + "10.6.10-MariaDB-log": 13, + "10.11.3-MariaDB-1:10.11.3+maria~deb11": 3, + "10.6.14-MariaDB-log": 4, + "10.5.17-MariaDB-1:10.5.17+maria~ubu2004": 9, + "8.0.34": 8, + "8.0.22": 2, + "10.3.39-MariaDB-0+deb10u1": 12, + "10.9.7-MariaDB-1:10.9.7+maria~deb11": 4, + "10.6.10-MariaDB-1:10.6.10+maria~ubu1804": 1, + "10.5.22-MariaDB-1:10.5.22+maria~ubu2004-log": 2, + "10.11.4-MariaDB-1~deb12u1-log": 9, + "10.11.3-MariaDB-1:10.11.3+maria~ubu2204-log": 3, + "10.8.3-MariaDB": 1, + "10.4.17-MariaDB-1:10.4.17+maria~bionic-log": 1, + "10.4.19-MariaDB-1:10.4.19+maria~focal": 1, + "10.8.6-MariaDB-1:10.8.6+maria~ubu2204": 4, + "10.3.37-MariaDB": 7, + "10.5.8-MariaDB": 2, + "8.0.25": 6, + "10.11.4-MariaDB-1": 5, + "10.6.12-MariaDB-1:10.6.12+maria~ubu1804": 2, + "10.5.21-MariaDB": 1, + "10.7.7-MariaDB-1:10.7.7+maria~ubu2004": 2, + "10.11.6-MariaDB-1": 1, + "10.6.11-MariaDB-0ubuntu0.22.04.1": 3, + "10.3.38-MariaDB-0ubuntu0.20.04.1": 11, + "10.11.5-MariaDB-1:10.11.5+maria~deb11": 8, + "8.0.24": 2, + "11.1.0-MariaDB-log": 1, + "10.11.6-MariaDB-1:10.11.6+maria~ubu2204-log": 5, + "10.6.12-MariaDB-1:10.6.12+maria~ubu2004-log": 3, + "10.6.5-MariaDB-1:10.6.5+maria~focal": 2, + "10.5.17-MariaDB": 3, + "11.0.2-MariaDB": 4, + "11.0.2-MariaDB-1:11.0.2+maria~ubu2204-log": 1, + "10.3.7-MariaDB": 2, + "10.10.6-MariaDB-1:10.10.6+maria~deb11": 2, + "8.0.23": 5, + "10.6.16-MariaDB": 5, + "8.0.33": 20, + "10.9.5-MariaDB-1:10.9.5+maria~ubu2204": 5, + "11.1.2-MariaDB-1:11.1.2+maria~deb11": 6, + "10.6.12-MariaDB-0ubuntu0.22.10.1": 2, + "10.6.15-MariaDB-log": 1, + "10.11.6-MariaDB-1:10.11.6+maria~deb12": 3, + "10.10.6-MariaDB-1:10.10.6+maria~ubu2004": 2, + "10.9.3-MariaDB-1:10.9.3+maria~ubu2204-log": 2, + "10.6.13-MariaDB-log": 14, + "10.5.23-MariaDB-log": 2, + "8.0.34-26.1": 1, + "10.10.3-MariaDB-1:10.10.3+maria~ubu2004": 3, + "11.1.3-MariaDB-1:11.1.3+maria~deb12-log": 1, + "10.6.11-MariaDB-1:10.6.11+maria~ubu2004": 12, + "10.6.11-MariaDB-1:10.6.11+maria~deb10": 1, + "10.8.6-MariaDB-1:10.8.6+maria~deb11": 5, + "8.0.33-0ubuntu0.20.04.2": 1, + "11.1.2-MariaDB-log": 2, + "10.3.39-MariaDB-1:10.3.39+maria~ubu2004": 1, + "10.4.28-MariaDB": 2, + "8.0.31-google": 4, + "10.3.37-MariaDB-0ubuntu0.20.04.1": 1, + "10.8.4-MariaDB-1:10.8.4+maria~ubu2204": 8, + "10.5.20-MariaDB-1:10.5.20+maria~ubu2004": 2, + "11.0.4-MariaDB-log": 1, + "10.5.21-MariaDB-log": 2, + "10.9.2-MariaDB-1:10.9.2+maria~deb11": 1, + "10.5.22-MariaDB-log": 1, + "11.0.4-MariaDB-1:11.0.4+maria~deb11": 8, + "10.11.6-MariaDB-1-log": 1, + "10.5.18-MariaDB-0+deb11u1-log": 3, + "10.6.8-MariaDB": 13, + "10.5.22-MariaDB-1:10.5.22+maria~ubu2004": 5, + "11.0.1-MariaDB": 1, + "11.1.3-MariaDB-1:11.1.3+maria~deb11": 4, + "8.0.30-0ubuntu0.20.04.2": 1, + "10.3.36-MariaDB-0+deb10u2": 3, + "8.0.31-23": 1, + "10.6.13-MariaDB": 3, + "10.10.5-MariaDB-1:10.10.5+maria~ubu2004": 1, + "10.5.23-MariaDB-1:10.5.23+maria~ubu2004-log": 5, + "10.5.23-MariaDB-1:10.5.23+maria~deb11": 3, + "10.3.29-MariaDB-log": 2, + "11.0.3-MariaDB-1:11.0.3+maria~ubu2204-log": 1, + "10.5.16-MariaDB": 1, + "8.0.32-0ubuntu0.20.04.2": 1, + "10.6.16-MariaDB-1:10.6.16+maria~ubu2204": 1, + "8.0.35-1ubuntu2": 1, + "10.11.3-MariaDB-1:10.11.3+maria~deb11-log": 1, + "10.9.8-MariaDB-1:10.9.8+maria~deb10": 2, + "10.4.21-MariaDB-1:10.4.21+maria~focal": 1, + "10.4.26-MariaDB": 1, + "11.2.2-MariaDB-1:11.2.2+maria~ubu2004": 2, + "10.6.11-MariaDB-1:10.6.11+maria~ubu2004-log": 1, + "10.3.27-MariaDB-0+deb10u1": 3, + "10.8.8-MariaDB-1:10.8.8+maria~ubu2004": 1, + "10.11.6-MariaDB-1:10.11.6+maria~deb10": 1, + "10.9.5-MariaDB-1:10.9.5+maria~deb11": 2, + "10.11.6-MariaDB-1:10.11.6+maria~deb12-log": 1, + "10.9.8-MariaDB-1:10.9.8+maria~ubu2204": 7, + "10.5.22-MariaDB-1:10.5.22+maria~ubu1804": 2, + "10.11.5-MariaDB-3": 2, + "10.6.15-MariaDB-1:10.6.15+maria~ubu2004": 6, + "10.11.4-MariaDB-1:10.11.4+maria~deb11": 3, + "11.3.1-MariaDB": 1, + "10.5.22-MariaDB-1:10.5.22+maria~ras11": 1, + "10.6.14-MariaDB-1:10.6.14+maria~ubu2204": 1, + "8.0.26-google": 1, + "8.0.13": 1, + "10.5.20-MariaDB-1:10.5.20+maria~ras10": 1, + "11.0.3-MariaDB": 1, + "10.5.15-MariaDB-0+deb11u1": 4, + "11.0.4-MariaDB-1:11.0.4+maria~ubu2204-log": 1, + "10.5.13-MariaDB-log": 1, + "10.5.13-MariaDB": 2, + "10.11.6-MariaDB-2": 1, + "10.5.8-MariaDB-1:10.5.8+maria~focal": 1, + "10.10.6-MariaDB-1:10.10.6+maria~deb10": 1, + "8.0.35-1": 1, + "8.0.21": 2, + "10.3.39-MariaDB-0+deb10u1-log": 1, + "10.3.37-MariaDB-log": 1, + "8.0.18": 1, + "10.7.3-MariaDB": 3, + "10.6.10-MariaDB-1:10.6.10+maria~ubu2004": 1, + "10.6.16-MariaDB-cll-lve": 1, + "10.6.13-MariaDB-1:10.6.13+maria~ubu2204-log": 1, + "10.6.15-MariaDB-1:10.6.15+maria~deb10-log": 1, + "10.11.3-MariaDB-1+rpi1-log": 1, + "8.0.29-0ubuntu0.20.04.3": 2, + "10.3.31-MariaDB-0+deb10u1": 1, + "10.5.16-MariaDB-log": 1, + "10.4.24-MariaDB": 1, + "10.6.13-MariaDB-1:10.6.13+maria~ubu2004": 1, + "11.1.3-MariaDB": 4, + "8.0.20-0ubuntu0.19.10.1": 1, + "11.0.2-MariaDB-1:11.0.2+maria~deb11": 3, + "10.3.39-MariaDB-1:10.3.39+maria~ubu1804": 1, + "10.3.39-MariaDB-1:10.3.39+maria~ubu1804-log": 1, + "10.10.6-MariaDB-1:10.10.6+maria~ubu2204": 1, + "8.0.35-0ubuntu0.23.04.1": 2, + "11.0.2-MariaDB-log": 1, + "10.10.2-MariaDB-1:10.10.2+maria~ubu2204-log": 1, + "10.10.2-MariaDB-1:10.10.2+maria~ubu2004": 1, + "11.2.2-MariaDB-1:11.2.2+maria~ubu2304": 1, + "8.0.34-0ubuntu0.20.04.1": 1, + "10.6.9-MariaDB-1:10.6.9+maria~ubu2004": 2, + "8.0.35-0ubuntu0.23.10.1": 1, + "10.6.15-MariaDB-cll-lve-log": 1, + "10.5.15-MariaDB-log": 1, + "8.0.33-0ubuntu0.22.04.2": 1, + "10.7.6-MariaDB-1:10.7.6+maria~deb11": 1, + "10.5.19-MariaDB-0+deb11u2-log": 1, + "10.9.8-MariaDB": 1, + "10.6.16-MariaDB-1:10.6.16+maria~deb10": 1, + "10.3.39-MariaDB-log-cll-lve": 2, + "10.5.19-MariaDB-1:10.5.19+maria~ubu1804": 1, + "10.10.7-MariaDB-1:10.10.7+maria~ubu2004": 1, + "10.7.7-MariaDB-1:10.7.7+maria~deb11": 2, + "8.0.20": 1, + "10.11.4-MariaDB-1:10.11.4+maria~ubu2004": 2, + "8.0.32-0ubuntu0.22.04.2": 3, + "10.5.15-MariaDB": 1, + "10.10.7-MariaDB-log": 1, + "10.5.21-MariaDB-1:10.5.21+maria~ubu2004": 3, + "10.8.3-MariaDB-1:10.8.3+maria~jammy": 1, + "10.11.5-MariaDB-1:10.11.5+maria~ubu2204-log": 2, + "10.6.15-MariaDB": 3, + "10.4.14-MariaDB-1:10.4.14+maria~bionic": 1, + "10.7.6-MariaDB-1:10.7.6+maria~ubu2004": 1, + "10.6.9-MariaDB-log": 2, + "10.11.2-MariaDB-1:10.11.2+maria~deb10": 2, + "10.10.7-MariaDB": 1, + "8.0.32-1": 1, + "10.9.4-MariaDB-1:10.9.4+maria~deb11": 3, + "8.0.34-Vitess": 1, + "8.0.29-21": 1, + "10.5.19-MariaDB-1:10.5.19+maria~ras10": 1, + "10.10.6-MariaDB": 1, + "10.5.10-MariaDB-1:10.5.10+maria~bionic": 1, + "10.4.25-MariaDB-1:10.4.25+maria~focal": 1 + }, + "count_configured": 17679 + }, + "postgresql": { + "versions": { + "16.0 (Debian 16.0-1.pgdg110+1)": 92, + "13.1 (Debian 13.1-1.pgdg100+1)": 912, + "16.1": 39, + "15.5 (Debian 15.5-1.pgdg120+1)": 67, + "15.3 (Debian 15.3-1.pgdg120+1)": 16, + "15.2 (Debian 15.2-1.pgdg110+1)": 36, + "14.2": 5, + "14.10 (Debian 14.10-1.pgdg120+1)": 60, + "12.2 (Debian 12.2-2.pgdg100+1)": 5, + "14.10 (Ubuntu 14.10-0ubuntu0.22.04.1)": 27, + "13.5 (Debian 13.5-1.pgdg110+1)": 2, + "15.4": 28, + "15.3": 136, + "13.13 (Debian 13.13-0+deb11u1)": 24, + "14.3 (Debian 14.3-1.pgdg110+1)": 8, + "15.4 (Debian 15.4-1.pgdg120+1)": 10, + "16.1 (Debian 16.1-1.pgdg120+1)": 72, + "14.4 (Debian 14.4-1.pgdg110+1)": 7, + "15.3 (Debian 15.3-1.pgdg110+1)": 71, + "13.13": 11, + "13.3 (Debian 13.3-1.pgdg100+1)": 3, + "15.4 (Debian 15.4-3)": 2, + "14.7 (Debian 14.7-1.pgdg110+1)": 9, + "14.1 (Debian 14.1-1.pgdg110+1)": 8, + "13.13 (Debian 13.13-1.pgdg120+1)": 19, + "15.5 (Debian 15.5-0+deb12u1)": 38, + "14.10 (Ubuntu 14.10-1.pgdg22.04+1)": 4, + "14.7 (Ubuntu 14.7-1.pgdg22.04+1)": 8, + "15.5": 41, + "13.10": 3, + "14.9": 13, + "14.5 (Debian 14.5-2.pgdg110+2)": 10, + "12.9 (Ubuntu 12.9-0ubuntu0.20.04.1)": 1, + "14.8 (Debian 14.8-1.pgdg100+1)": 1, + "14.7": 9, + "14.2 (Debian 14.2-1.pgdg110+1)": 11, + "12.11 (Ubuntu 12.11-1.pgdg18.04+1)": 1, + "14.3": 7, + "16.0 (Debian 16.0-1.pgdg120+1)": 30, + "16.1 (Debian 16.1-1.pgdg110+1)": 30, + "13.4 (Debian 13.4-4.pgdg110+1)": 6, + "12.8 (Ubuntu 12.8-0ubuntu0.20.04.1)": 1, + "14.8 (Debian 14.8-1.pgdg110+1)": 4, + "15.2 (Ubuntu 15.2-1.pgdg22.04+1)": 12, + "13.4 (Debian 13.4-1.pgdg100+1)": 1, + "13.11 (Raspbian 13.11-0+deb11u1)": 3, + "14.1": 13, + "12.3 (Debian 12.3-1.pgdg100+1)": 2, + "12.16 (Debian 12.16-1.pgdg120+1)": 2, + "15.1": 6, + "12.11 (Debian 12.11-1.pgdg110+1)": 2, + "12.17 (Ubuntu 12.17-0ubuntu0.20.04.1)": 8, + "14.9 (Debian 14.9-1.pgdg100+1)": 2, + "15.5 (Ubuntu 15.5-1.pgdg22.04+1)": 8, + "16.1 (Ubuntu 16.1-1.pgdg22.04+1)": 10, + "12.5": 4, + "13.2 (Debian 13.2-1.pgdg100+1)": 5, + "14.9 (Debian 14.9-1.pgdg120+1)": 5, + "14.5": 18, + "14.6": 4, + "16.0": 5, + "15.4 (Debian 15.4-2.pgdg110+1)": 4, + "15.2": 6, + "14.10": 38, + "14.5 (Debian 14.5-1.pgdg110+1)": 10, + "12.15 (Debian 12.15-1.pgdg110+1)": 2, + "13.11 (Debian 13.11-0+deb11u1)": 6, + "12.11 (Ubuntu 12.11-0ubuntu0.20.04.1)": 3, + "15.1 (Ubuntu 15.1-1.pgdg22.04+1)": 1, + "15.4 (Debian 15.4-1.pgdg110+1)": 4, + "14.9 (Ubuntu 14.9-0ubuntu0.22.04.1)": 9, + "15.4 (Debian 15.4-1.pgdg100+1)": 1, + "13.8 (Debian 13.8-1.pgdg110+1)": 3, + "14.8": 5, + "13.9": 2, + "12.17 (Debian 12.17-1.pgdg120+1)": 12, + "14.7 (Ubuntu 14.7-0ubuntu0.22.04.1)": 1, + "12.14 (Ubuntu 12.14-0ubuntu0.20.04.1)": 3, + "13.11": 3, + "12.15 (Ubuntu 12.15-1.pgdg18.04+1)": 2, + "15.4 (Ubuntu 15.4-1ubuntu1)": 1, + "12.16 (Debian 12.16-1.pgdg110+1)": 1, + "12.15 (Ubuntu 12.15-0ubuntu0.20.04.1)": 1, + "16.0 (Debian 16.0-1.pgdg100+1)": 1, + "15.1 (Debian 15.1-1.pgdg110+1)": 10, + "12.6": 1, + "13.0 (Debian 13.0-1.pgdg100+1)": 1, + "13.9 (Debian 13.9-1.pgdg110+1)": 2, + "14.10 (Debian 14.10-1.pgdg110+1)": 11, + "14.10 (Ubuntu 14.10-1.pgdg20.04+1)": 2, + "14.8 (Debian 14.8-1.pgdg120+1)": 4, + "12.13 (Debian 12.13-1.pgdg110+1)": 4, + "14.6 (Debian 14.6-1.pgdg110+1)": 7, + "12.9": 1, + "13.12 (Debian 13.12-1.pgdg120+1)": 8, + "13.0": 2, + "13.6 (Debian 13.6-1.pgdg110+1)": 4, + "12.14": 2, + "13.10 (Debian 13.10-1.pgdg110+1)": 3, + "16.1 (Ubuntu 16.1-1.pgdg20.04+1)": 1, + "12.15 (Debian 12.15-1.pgdg120+1)": 3, + "12.7 (Debian 12.7-1.pgdg100+1)": 1, + "13.13 (Raspbian 13.13-0+deb11u1)": 4, + "12.14 (Ubuntu 12.14-1.pgdg22.04+1)": 1, + "13.5": 1, + "12.5 (Debian 12.5-1.pgdg100+1)": 1, + "12.17": 10, + "12.12": 2, + "13.7 (Ubuntu 13.7-0ubuntu0.21.10.1)": 1, + "13.9 (Debian 13.9-0+deb11u1)": 3, + "12.15": 1, + "13.2": 3, + "12.7": 3, + "12.3": 1, + "15.4 (Ubuntu 15.4-2.pgdg23.04+1)": 2, + "15.3 (Debian 15.3-0+deb12u1)": 2, + "13.12 (Debian 13.12-1.pgdg110+1)": 1, + "13.8": 1, + "15.4 (Debian 15.4-2.pgdg120+1)": 9, + "12.16 (Ubuntu 12.16-0ubuntu0.20.04.1)": 2, + "15.3 (Ubuntu 15.3-1.pgdg22.04+1)": 3, + "16.1 (Debian 16.1-1)": 1, + "13.12": 4, + "14.6 (Ubuntu 14.6-1.pgdg22.04+1)": 1, + "12.8": 1, + "13.7 (Debian 13.7-1.pgdg110+1)": 2, + "12.4": 1, + "15.1 (Ubuntu 15.1-1.pgdg18.04+1)": 1, + "14.9 (Debian 14.9-1.pgdg110+1)": 2, + "14.0 (Debian 14.0-1.pgdg110+1)": 4, + "13.7 (Debian 13.7-0+deb11u1)": 2, + "15.5 (Debian 15.5-1.pgdg110+1)": 3, + "12.10": 2, + "12.14 (Debian 12.14-1.pgdg110+1)": 3, + "12.13 (Ubuntu 12.13-0ubuntu0.20.04.1)": 1, + "12.2": 1, + "13.9 (Debian 13.9-1.pgdg100+1)": 1, + "14.8 (Ubuntu 14.8-1.pgdg20.04+1)": 1, + "15.0 (Debian 15.0-1.pgdg110+1)": 4, + "13.8 (Debian 13.8-0+deb11u1)": 2, + "13.5 (Debian 13.5-1.pgdg100+1)": 1, + "13.11 (Debian 13.11-1.pgdg110+1)": 2, + "13.4": 3, + "15.0": 3, + "12.17 (Debian 12.17-1.pgdg110+1)": 2, + "14.7 (Ubuntu 14.7-1.pgdg20.04+1)": 1, + "13.11 (Debian 13.11-1.pgdg100+1)": 1, + "15.5 (Ubuntu 15.5-0ubuntu0.23.10.1)": 1, + "14.5 (Ubuntu 14.5-2.pgdg20.04+2)": 1, + "14.2 (Ubuntu 14.2-1ubuntu1)": 1, + "12.11": 1, + "13.7": 2, + "15.4 (Ubuntu 15.4-2.pgdg22.04+1)": 2, + "13.12 (Debian 13.12-1.pgdg100+1)": 1, + "14.4": 2, + "16.0 (Ubuntu 16.0-1.pgdg22.04+1)": 2, + "14.0": 2, + "14.2 (Ubuntu 14.2-1.pgdg20.04+1)": 1, + "12.16": 1, + "13.5 (Ubuntu 13.5-0ubuntu0.21.04.1)": 1, + "12.17 (Ubuntu 12.17-1.pgdg20.04+1)": 1, + "13.3": 2, + "14.8 (Ubuntu 14.8-1.pgdg22.04+1)": 1, + "14.2 (Ubuntu 14.2-1.pgdg18.04+1)": 1, + "15.5 (Debian 15.5-1.pgdg100+1)": 1, + "12.8 (Debian 12.8-1.pgdg100+1)": 1, + "14.10 (Ubuntu 14.10-1.pgdg23.04+1)": 1, + "15.5 (Ubuntu 15.5-1.pgdg20.04+1)": 1, + "13.6": 1 + }, + "count_configured": 2312 + } + } + }, + "extended_data_from": 310156 +} diff --git a/tests/components/analytics_insights/fixtures/integrations.json b/tests/components/analytics_insights/fixtures/integrations.json new file mode 100644 index 00000000000..eb42216c232 --- /dev/null +++ b/tests/components/analytics_insights/fixtures/integrations.json @@ -0,0 +1,9 @@ +{ + "youtube": { + "title": "YouTube", + "description": "Instructions on how to integrate YouTube within Home Assistant.", + "quality_scale": "", + "iot_class": "Cloud Polling", + "integration_type": "service" + } +} diff --git a/tests/components/analytics_insights/snapshots/test_sensor.ambr b/tests/components/analytics_insights/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..8a9688cb60c --- /dev/null +++ b/tests/components/analytics_insights/snapshots/test_sensor.ambr @@ -0,0 +1,142 @@ +# serializer version: 1 +# name: test_all_entities[sensor.homeassistant_analytics_myq-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.homeassistant_analytics_myq', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'myq', + 'platform': 'analytics_insights', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'core_myq_active_installations', + 'unit_of_measurement': 'active installations', + }) +# --- +# name: test_all_entities[sensor.homeassistant_analytics_myq-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Homeassistant Analytics myq', + 'state_class': , + 'unit_of_measurement': 'active installations', + }), + 'context': , + 'entity_id': 'sensor.homeassistant_analytics_myq', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_all_entities[sensor.homeassistant_analytics_spotify-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.homeassistant_analytics_spotify', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'spotify', + 'platform': 'analytics_insights', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'core_spotify_active_installations', + 'unit_of_measurement': 'active installations', + }) +# --- +# name: test_all_entities[sensor.homeassistant_analytics_spotify-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Homeassistant Analytics spotify', + 'state_class': , + 'unit_of_measurement': 'active installations', + }), + 'context': , + 'entity_id': 'sensor.homeassistant_analytics_spotify', + 'last_changed': , + 'last_updated': , + 'state': '24388', + }) +# --- +# name: test_all_entities[sensor.homeassistant_analytics_youtube-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.homeassistant_analytics_youtube', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'YouTube', + 'platform': 'analytics_insights', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'core_youtube_active_installations', + 'unit_of_measurement': 'active installations', + }) +# --- +# name: test_all_entities[sensor.homeassistant_analytics_youtube-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Homeassistant Analytics YouTube', + 'state_class': , + 'unit_of_measurement': 'active installations', + }), + 'context': , + 'entity_id': 'sensor.homeassistant_analytics_youtube', + 'last_changed': , + 'last_updated': , + 'state': '339', + }) +# --- diff --git a/tests/components/analytics_insights/test_config_flow.py b/tests/components/analytics_insights/test_config_flow.py new file mode 100644 index 00000000000..4046bd040df --- /dev/null +++ b/tests/components/analytics_insights/test_config_flow.py @@ -0,0 +1,70 @@ +"""Test the Homeassistant Analytics config flow.""" +from unittest.mock import AsyncMock + +from python_homeassistant_analytics import HomeassistantAnalyticsConnectionError + +from homeassistant import config_entries +from homeassistant.components.analytics_insights.const import ( + CONF_TRACKED_INTEGRATIONS, + DOMAIN, +) +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + + +async def test_form( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_analytics_client: AsyncMock +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TRACKED_INTEGRATIONS: ["youtube"]}, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Home Assistant Analytics Insights" + assert result["data"] == {} + assert result["options"] == {CONF_TRACKED_INTEGRATIONS: ["youtube"]} + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect( + hass: HomeAssistant, mock_analytics_client: AsyncMock +) -> None: + """Test we handle cannot connect error.""" + + mock_analytics_client.get_integrations.side_effect = ( + HomeassistantAnalyticsConnectionError + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "cannot_connect" + + +async def test_form_already_configured( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test we handle cannot connect error.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={}, + options={CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify"]}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/analytics_insights/test_init.py b/tests/components/analytics_insights/test_init.py new file mode 100644 index 00000000000..08b898f13c1 --- /dev/null +++ b/tests/components/analytics_insights/test_init.py @@ -0,0 +1,28 @@ +"""Test the Home Assistant analytics init module.""" +from __future__ import annotations + +from unittest.mock import AsyncMock + +from homeassistant.components.analytics_insights.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.analytics_insights import setup_integration + + +async def test_load_unload_entry( + hass: HomeAssistant, + mock_analytics_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test load and unload entry.""" + await setup_integration(hass, mock_config_entry) + entry = hass.config_entries.async_entries(DOMAIN)[0] + + assert entry.state == ConfigEntryState.LOADED + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.NOT_LOADED diff --git a/tests/components/analytics_insights/test_sensor.py b/tests/components/analytics_insights/test_sensor.py new file mode 100644 index 00000000000..83ea2885456 --- /dev/null +++ b/tests/components/analytics_insights/test_sensor.py @@ -0,0 +1,86 @@ +"""Test the Home Assistant analytics sensor module.""" +from datetime import timedelta +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +from python_homeassistant_analytics import ( + HomeassistantAnalyticsConnectionError, + HomeassistantAnalyticsNotModifiedError, +) +from syrupy import SnapshotAssertion + +from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_analytics_client: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + with patch( + "homeassistant.components.analytics_insights.PLATFORMS", + [Platform.SENSOR], + ): + await setup_integration(hass, mock_config_entry) + entity_entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + + assert entity_entries + for entity_entry in entity_entries: + assert hass.states.get(entity_entry.entity_id) == snapshot( + name=f"{entity_entry.entity_id}-state" + ) + assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") + + +async def test_connection_error( + hass: HomeAssistant, + mock_analytics_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test connection error.""" + await setup_integration(hass, mock_config_entry) + + mock_analytics_client.get_current_analytics.side_effect = ( + HomeassistantAnalyticsConnectionError() + ) + freezer.tick(delta=timedelta(hours=12)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert ( + hass.states.get("sensor.homeassistant_analytics_spotify").state + == STATE_UNAVAILABLE + ) + + +async def test_data_not_modified( + hass: HomeAssistant, + mock_analytics_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test not updating data if its not modified.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("sensor.homeassistant_analytics_spotify").state == "24388" + mock_analytics_client.get_current_analytics.side_effect = ( + HomeassistantAnalyticsNotModifiedError + ) + freezer.tick(delta=timedelta(hours=12)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + mock_analytics_client.get_current_analytics.assert_called() + assert hass.states.get("sensor.homeassistant_analytics_spotify").state == "24388"