Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions homeassistant/components/airthings/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ async def async_step_user(
)

errors = {}
await self.async_set_unique_id(user_input[CONF_ID])
self._abort_if_unique_id_configured()

try:
await airthings.get_token(
Expand All @@ -60,9 +62,6 @@ async def async_step_user(
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(user_input[CONF_ID])
self._abort_if_unique_id_configured()

return self.async_create_entry(title="Airthings", data=user_input)

return self.async_show_form(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/airthings/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async def async_setup_entry(

coordinator = entry.runtime_data
entities = [
AirthingsHeaterEnergySensor(
AirthingsDeviceSensor(
coordinator,
airthings_device,
SENSORS[sensor_types],
Expand All @@ -162,7 +162,7 @@ async def async_setup_entry(
async_add_entities(entities)


class AirthingsHeaterEnergySensor(
class AirthingsDeviceSensor(
CoordinatorEntity[AirthingsDataUpdateCoordinator], SensorEntity
):
"""Representation of a Airthings Sensor device."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/alexa_devices/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "silver",
"requirements": ["aioamazondevices==3.5.0"]
"requirements": ["aioamazondevices==3.5.1"]
}
3 changes: 3 additions & 0 deletions homeassistant/components/analytics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from .analytics import Analytics
from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
from .http import AnalyticsDevicesView

CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

Expand Down Expand Up @@ -55,6 +56,8 @@ def start_schedule(_event: Event) -> None:
websocket_api.async_register_command(hass, websocket_analytics)
websocket_api.async_register_command(hass, websocket_analytics_preferences)

hass.http.register_view(AnalyticsDevicesView)

hass.data[DATA_COMPONENT] = analytics
return True

Expand Down
89 changes: 87 additions & 2 deletions homeassistant/components/analytics/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from homeassistant.const import ATTR_DOMAIN, BASE_PLATFORMS, __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.hassio import is_hassio
from homeassistant.helpers.storage import Store
Expand Down Expand Up @@ -77,6 +77,11 @@
)


def gen_uuid() -> str:
"""Generate a new UUID."""
return uuid.uuid4().hex


@dataclass
class AnalyticsData:
"""Analytics data."""
Expand Down Expand Up @@ -184,7 +189,7 @@ async def send_analytics(self, _: datetime | None = None) -> None:
return

if self._data.uuid is None:
self._data.uuid = uuid.uuid4().hex
self._data.uuid = gen_uuid()
await self._store.async_save(dataclass_asdict(self._data))

if self.supervisor:
Expand Down Expand Up @@ -381,3 +386,83 @@ def _domains_from_yaml_config(yaml_configuration: dict[str, Any]) -> set[str]:
).values():
domains.update(platforms)
return domains


async def async_devices_payload(hass: HomeAssistant) -> dict:
"""Return the devices payload."""
integrations_without_model_id: set[str] = set()
devices: list[dict[str, Any]] = []
dev_reg = dr.async_get(hass)
# Devices that need via device info set
new_indexes: dict[str, int] = {}
via_devices: dict[str, str] = {}

seen_integrations = set()

for device in dev_reg.devices.values():
# Ignore services
if device.entry_type:
continue

if not device.primary_config_entry:
continue

config_entry = hass.config_entries.async_get_entry(device.primary_config_entry)

if config_entry is None:
continue

seen_integrations.add(config_entry.domain)

if not device.model_id:
integrations_without_model_id.add(config_entry.domain)
continue

if not device.manufacturer:
continue

new_indexes[device.id] = len(devices)
devices.append(
{
"integration": config_entry.domain,
"manufacturer": device.manufacturer,
"model_id": device.model_id,
"model": device.model,
"sw_version": device.sw_version,
"hw_version": device.hw_version,
"has_suggested_area": device.suggested_area is not None,
"has_configuration_url": device.configuration_url is not None,
"via_device": None,
}
)
if device.via_device_id:
via_devices[device.id] = device.via_device_id

for from_device, via_device in via_devices.items():
if via_device not in new_indexes:
continue
devices[new_indexes[from_device]]["via_device"] = new_indexes[via_device]

integrations = {
domain: integration
for domain, integration in (
await async_get_integrations(hass, seen_integrations)
).items()
if isinstance(integration, Integration)
}

for device_info in devices:
if integration := integrations.get(device_info["integration"]):
device_info["is_custom_integration"] = not integration.is_built_in

return {
"version": "home-assistant:1",
"no_model_id": sorted(
[
domain
for domain in integrations_without_model_id
if domain in integrations and integrations[domain].is_built_in
]
),
"devices": devices,
}
27 changes: 27 additions & 0 deletions homeassistant/components/analytics/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""HTTP endpoints for analytics integration."""

from aiohttp import web

from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin
from homeassistant.core import HomeAssistant

from .analytics import async_devices_payload


class AnalyticsDevicesView(HomeAssistantView):
"""View to handle analytics devices payload download requests."""

url = "/api/analytics/devices"
name = "api:analytics:devices"

@require_admin
async def get(self, request: web.Request) -> web.Response:
"""Return analytics devices payload as JSON."""
hass: HomeAssistant = request.app[KEY_HASS]
payload = await async_devices_payload(hass)
return self.json(
payload,
headers={
"Content-Disposition": "attachment; filename=analytics_devices.json"
},
)
2 changes: 1 addition & 1 deletion homeassistant/components/analytics/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Analytics",
"after_dependencies": ["energy", "hassio", "recorder"],
"codeowners": ["@home-assistant/core", "@ludeeus"],
"dependencies": ["api", "websocket_api"],
"dependencies": ["api", "websocket_api", "http"],
"documentation": "https://www.home-assistant.io/integrations/analytics",
"integration_type": "system",
"iot_class": "cloud_push",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dsmr/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
"data": {
"time_between_update": "Minimum time between entity updates [s]"
},
"title": "DSMR Options"
"title": "DSMR options"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dsmr_reader/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
"issues": {
"cannot_subscribe_mqtt_topic": {
"title": "Cannot subscribe to MQTT topic {topic_title}",
"description": "The DSMR Reader integration cannot subscribe to the MQTT topic: `{topic}`. Please check the configuration of the MQTT broker and the topic.\nDSMR Reader needs to be running, before starting this integration."
"description": "The DSMR Reader integration cannot subscribe to the MQTT topic: `{topic}`. Please check the configuration of the MQTT broker and the topic.\nDSMR Reader needs to be running before starting this integration."
}
}
}
Loading
Loading