1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 15:44:52 +01:00

Dynamically add new devices to Libre Hardware Monitor (#165250)

This commit is contained in:
Sab44
2026-03-10 09:19:50 +01:00
committed by GitHub
parent cf454a1fa3
commit 0fa666518e
3 changed files with 80 additions and 41 deletions

View File

@@ -62,7 +62,9 @@ class LibreHardwareMonitorCoordinator(DataUpdateCoordinator[LibreHardwareMonitor
registry=dr.async_get(self.hass), config_entry_id=self._entry_id
)
self._previous_devices: dict[DeviceId, DeviceName] = {
DeviceId(next(iter(device.identifiers))[1]): DeviceName(device.name)
DeviceId(
next(iter(device.identifiers))[1].removeprefix(f"{self._entry_id}_")
): DeviceName(device.name)
for device in device_entries
if device.identifiers and device.name
}
@@ -109,11 +111,6 @@ class LibreHardwareMonitorCoordinator(DataUpdateCoordinator[LibreHardwareMonitor
self, detected_devices: dict[DeviceId, DeviceName]
) -> None:
"""Handle device changes by deleting devices from / adding devices to Home Assistant."""
detected_devices = {
DeviceId(f"{self.config_entry.entry_id}_{detected_id}"): device_name
for detected_id, device_name in detected_devices.items()
}
previous_device_ids = set(self._previous_devices.keys())
detected_device_ids = set(detected_devices.keys())
@@ -131,25 +128,14 @@ class LibreHardwareMonitorCoordinator(DataUpdateCoordinator[LibreHardwareMonitor
device_registry = dr.async_get(self.hass)
for device_id in orphaned_devices:
if device := device_registry.async_get_device(
identifiers={(DOMAIN, device_id)}
identifiers={(DOMAIN, f"{self._entry_id}_{device_id}")}
):
_LOGGER.debug(
"Removing device: %s", self._previous_devices[device_id]
)
device_registry.async_update_device(
device_id=device.id,
remove_config_entry_id=self.config_entry.entry_id,
remove_config_entry_id=self._entry_id,
)
if self.data is None:
# initial update during integration startup
self._previous_devices = detected_devices # type: ignore[unreachable]
return
if new_devices := detected_device_ids - previous_device_ids:
_LOGGER.warning(
"New Device(s) detected, reload integration to add them to Home Assistant: %s",
[detected_devices[DeviceId(device_id)] for device_id in new_devices],
)
self._previous_devices = detected_devices

View File

@@ -2,9 +2,10 @@
from __future__ import annotations
import logging
from typing import Any
from librehardwaremonitor_api.model import LibreHardwareMonitorSensorData
from librehardwaremonitor_api.model import DeviceId, LibreHardwareMonitorSensorData
from librehardwaremonitor_api.sensor_type import SensorType
from homeassistant.components.sensor import SensorEntity, SensorStateClass
@@ -16,6 +17,8 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import LibreHardwareMonitorConfigEntry, LibreHardwareMonitorCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
STATE_MIN_VALUE = "min_value"
@@ -30,10 +33,28 @@ async def async_setup_entry(
"""Set up the LibreHardwareMonitor platform."""
lhm_coordinator = config_entry.runtime_data
async_add_entities(
LibreHardwareMonitorSensor(lhm_coordinator, config_entry.entry_id, sensor_data)
for sensor_data in lhm_coordinator.data.sensor_data.values()
)
known_devices: set[DeviceId] = set()
def _check_device() -> None:
current_devices = set(lhm_coordinator.data.main_device_ids_and_names)
new_devices = current_devices - known_devices
if new_devices:
_LOGGER.debug("New Device(s) detected, adding: %s", new_devices)
known_devices.update(new_devices)
new_devices_sensor_data = [
sensor_data
for sensor_data in lhm_coordinator.data.sensor_data.values()
if sensor_data.device_id in new_devices
]
async_add_entities(
LibreHardwareMonitorSensor(
lhm_coordinator, config_entry.entry_id, sensor_data
)
for sensor_data in new_devices_sensor_data
)
_check_device()
config_entry.async_on_unload(lhm_coordinator.async_add_listener(_check_device))
class LibreHardwareMonitorSensor(

View File

@@ -2,7 +2,6 @@
from dataclasses import replace
from datetime import timedelta
import logging
from types import MappingProxyType
from unittest.mock import AsyncMock
@@ -16,7 +15,9 @@ from librehardwaremonitor_api.model import (
DeviceId,
DeviceName,
LibreHardwareMonitorData,
LibreHardwareMonitorSensorData,
)
from librehardwaremonitor_api.sensor_type import SensorType
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -57,7 +58,6 @@ async def test_sensors_are_created(
)
async def test_sensors_go_unavailable_in_case_of_error_and_recover_after_successful_retry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_lhm_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
@@ -288,34 +288,66 @@ async def _mock_orphaned_device(
)
async def test_integration_does_not_log_new_devices_on_first_refresh(
async def test_integration_dynamically_adds_new_devices(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
mock_lhm_client: AsyncMock,
mock_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test that initial data update does not cause warning about new devices."""
mock_lhm_client.get_data.return_value = LibreHardwareMonitorData(
computer_name=mock_lhm_client.get_data.return_value.computer_name,
"""Test that new devices are created when detected."""
await init_integration(hass, mock_config_entry)
device_entries: list[DeviceEntry] = dr.async_entries_for_config_entry(
registry=device_registry, config_entry_id=mock_config_entry.entry_id
)
assert len(device_entries) == 3
mock_lhm_client.get_data.return_value = replace(
mock_lhm_client.get_data.return_value,
main_device_ids_and_names=MappingProxyType(
{
**mock_lhm_client.get_data.return_value.main_device_ids_and_names,
DeviceId("generic-memory"): DeviceName("Generic Memory"),
}
),
sensor_data=mock_lhm_client.get_data.return_value.sensor_data,
is_deprecated_version=False,
sensor_data=MappingProxyType(
{
**mock_lhm_client.get_data.return_value.sensor_data,
"generic-memory-test-sensor": LibreHardwareMonitorSensorData(
name="Test sensor",
value="30",
type=SensorType.FACTOR,
min="12",
max="36",
unit=None,
device_id="generic-memory",
device_name="Generic Memory",
device_type="MEMORY",
sensor_id="generic-memory-test-sensor",
),
}
),
)
with caplog.at_level(logging.WARNING):
await init_integration(hass, mock_config_entry)
freezer.tick(timedelta(DEFAULT_SCAN_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
libre_hardware_monitor_logs = [
record
for record in caplog.records
if record.name.startswith("homeassistant.components.libre_hardware_monitor")
]
assert len(libre_hardware_monitor_logs) == 0
device_entries: list[DeviceEntry] = dr.async_entries_for_config_entry(
registry=device_registry, config_entry_id=mock_config_entry.entry_id
)
assert len(device_entries) == 4
expected_device = next(entry for entry in device_entries if "Generic" in entry.name)
assert expected_device.name == "[GAMING-PC] Generic Memory"
entity_entries = er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
)
assert "sensor.gaming_pc_generic_memory_test_sensor" in [
entry.entity_id for entry in entity_entries
]
async def test_non_deprecated_version_does_not_raise_issue(