Add Sensor Device Classes to Libre Hardware Monitor (#182203)

This commit is contained in:
Sab44
2026-10-03 08:15:21 +02:00
committed by GitHub
parent 315b4af4b8
commit cb29a530bc
12 changed files with 607 additions and 93 deletions
@@ -2,11 +2,11 @@
import logging
from homeassistant.const import Platform
from homeassistant.const import Platform, UnitOfDataRate
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from .const import DOMAIN
from .const import DOMAIN, THROUGHPUT_UNIQUE_ID_FRAGMENT
from .coordinator import (
LibreHardwareMonitorConfigEntry,
LibreHardwareMonitorCoordinator,
@@ -20,8 +20,10 @@ _LOGGER = logging.getLogger(__name__)
async def async_migrate_entry(
hass: HomeAssistant, config_entry: LibreHardwareMonitorConfigEntry
) -> bool:
"""Migrate non-unique entity and device ids."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
"""Migrate to current config flow version."""
_LOGGER.debug(
"Migrating from version %s.%s", config_entry.version, config_entry.minor_version
)
if config_entry.version == 1:
# Migrate entity identifiers
@@ -61,11 +63,40 @@ async def async_migrate_entry(
)
hass.config_entries.async_update_entry(
config_entry, data=config_entry.data, version=2
config_entry, data=config_entry.data, version=2, minor_version=1
)
_LOGGER.debug("Migration to version 2 successful")
return True
_LOGGER.debug("Migration to version 2.1 successful")
if config_entry.version == 2 and config_entry.minor_version == 1:
# Migrate Throughput unit from KB/s to KiB/s
entity_registry = er.async_get(hass)
registry_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
throughput_entities = [
entry
for entry in registry_entries
if THROUGHPUT_UNIQUE_ID_FRAGMENT in entry.unique_id
]
for reg_entry in throughput_entities:
_LOGGER.debug(
"Migrating entity %s unit from %s to %s",
reg_entry.entity_id,
reg_entry.unit_of_measurement,
UnitOfDataRate.KIBIBYTES_PER_SECOND,
)
entity_registry.async_update_entity(
reg_entry.entity_id,
unit_of_measurement=UnitOfDataRate.KIBIBYTES_PER_SECOND,
)
hass.config_entries.async_update_entry(
config_entry, data=config_entry.data, version=2, minor_version=2
)
_LOGGER.debug("Migration to version 2.2 successful")
return True
@@ -58,6 +58,7 @@ class LibreHardwareMonitorConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for LibreHardwareMonitor."""
VERSION = 2
MINOR_VERSION = 2
def __init__(self) -> None:
"""Init config flow."""
@@ -4,3 +4,6 @@ DOMAIN = "libre_hardware_monitor"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8085
DEFAULT_SCAN_INTERVAL = 10
LEGACY_THROUGHPUT_UNIT = "KB/s"
THROUGHPUT_UNIQUE_ID_FRAGMENT = "throughput"
@@ -71,7 +71,7 @@ rules:
docs-use-cases: todo
dynamic-devices: done
entity-category: todo
entity-device-class: todo
entity-device-class: done
entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
@@ -0,0 +1,29 @@
"""Support for LibreHardwareMonitor Recorder Platform."""
from homeassistant.const import UnitOfDataRate
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from .const import DOMAIN, LEGACY_THROUGHPUT_UNIT, THROUGHPUT_UNIQUE_ID_FRAGMENT
@callback
def async_custom_equivalent_units(
hass: HomeAssistant,
) -> dict[str, dict[str | None, str]]:
"""Return custom equivalent units per entity id.
Throughput sensors reported the unit as "KB/s" before they got a device class.
"""
entity_registry = er.async_get(hass)
return {
registry_entry.entity_id: {
LEGACY_THROUGHPUT_UNIT: UnitOfDataRate.KIBIBYTES_PER_SECOND
}
for config_entry in hass.config_entries.async_entries(DOMAIN)
for registry_entry in er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
if THROUGHPUT_UNIQUE_ID_FRAGMENT in registry_entry.unique_id
}
@@ -6,7 +6,14 @@ from typing import Any, override
from librehardwaremonitor_api.model import DeviceId, LibreHardwareMonitorSensorData
from librehardwaremonitor_api.sensor_type import SensorType
from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.components.sensor import (
AMBIGUOUS_UNITS,
UNIT_CONVERTERS,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import UnitOfDataRate
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -22,6 +29,24 @@ PARALLEL_UPDATES = 0
STATE_MIN_VALUE = "min_value"
STATE_MAX_VALUE = "max_value"
DEVICE_CLASSES: dict[SensorType, SensorDeviceClass] = {
SensorType.VOLTAGE: SensorDeviceClass.VOLTAGE,
SensorType.CURRENT: SensorDeviceClass.CURRENT,
SensorType.POWER: SensorDeviceClass.POWER,
SensorType.CLOCK: SensorDeviceClass.FREQUENCY,
SensorType.FREQUENCY: SensorDeviceClass.FREQUENCY,
SensorType.TEMPERATURE: SensorDeviceClass.TEMPERATURE,
SensorType.FLOW: SensorDeviceClass.VOLUME_FLOW_RATE,
SensorType.DATA: SensorDeviceClass.DATA_SIZE,
SensorType.SMALL_DATA: SensorDeviceClass.DATA_SIZE,
SensorType.THROUGHPUT: SensorDeviceClass.DATA_RATE,
SensorType.TIMESPAN: SensorDeviceClass.DURATION,
SensorType.ENERGY: SensorDeviceClass.ENERGY_STORAGE,
SensorType.NOISE: SensorDeviceClass.SOUND_PRESSURE,
SensorType.CONDUCTIVITY: SensorDeviceClass.CONDUCTIVITY,
SensorType.HUMIDITY: SensorDeviceClass.HUMIDITY,
}
async def async_setup_entry(
hass: HomeAssistant,
@@ -74,6 +99,21 @@ class LibreHardwareMonitorSensor(
self._attr_name: str = sensor_data.name
if sensor_data.type is None:
_LOGGER.debug("Missing type for sensor: %s", sensor_data.name)
elif device_class := DEVICE_CLASSES.get(sensor_data.type):
self._attr_device_class = device_class
if device_class is SensorDeviceClass.DATA_RATE:
self._attr_suggested_unit_of_measurement = (
UnitOfDataRate.KIBIBYTES_PER_SECOND
)
# Device class default rounds throughput to whole KiB/s
self._attr_suggested_display_precision = 1
elif device_class is SensorDeviceClass.VOLTAGE:
# Device class default rounds voltages to whole volts
self._attr_suggested_display_precision = 3
self._set_state(sensor_data)
self._attr_unique_id: str = f"{entry_id}_{sensor_data.sensor_id}"
@@ -86,33 +126,39 @@ class LibreHardwareMonitorSensor(
model=sensor_data.device_type,
)
def _set_state(
self,
sensor_data: LibreHardwareMonitorSensorData,
) -> None:
value = sensor_data.value
min_value = sensor_data.min
max_value = sensor_data.max
unit = sensor_data.unit
def _set_state(self, sensor_data: LibreHardwareMonitorSensorData) -> None:
self._attr_native_value: str | None = sensor_data.value
self._attr_native_unit_of_measurement = sensor_data.unit
self._native_min_value = sensor_data.min
self._native_max_value = sensor_data.max
if sensor_data.type == SensorType.THROUGHPUT:
# Temporary fix: convert the B/s value to KB/s to not break existing entries
# This will be migrated properly once SensorDeviceClass is introduced
value = f"{(float(value) / 1024):.1f}" if value is not None else None
min_value = (
f"{(float(min_value) / 1024):.1f}" if min_value is not None else None
)
max_value = (
f"{(float(max_value) / 1024):.1f}" if max_value is not None else None
)
unit = "KB/s"
self._attr_native_value: str | None = value
self._attr_extra_state_attributes: dict[str, Any] = {
STATE_MIN_VALUE: min_value,
STATE_MAX_VALUE: max_value,
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return min and max values in the unit the state is reported in."""
return {
STATE_MIN_VALUE: self._value_in_state_unit(self._native_min_value),
STATE_MAX_VALUE: self._value_in_state_unit(self._native_max_value),
}
self._attr_native_unit_of_measurement = unit
def _value_in_state_unit(self, native_value: str | None) -> str | float | None:
"""Convert a native value to the unit the state is converted to."""
# Conductivity uses micro-sign U+00B5 spelling in its unit which cannot be converted
# so we swap it with Greek mu U+03BC to get μS/cm as a valid unit
native_unit = AMBIGUOUS_UNITS.get(
self.native_unit_of_measurement, self.native_unit_of_measurement
)
unit = self.unit_of_measurement
if (
native_value is None
or native_unit == unit
or (converter := UNIT_CONVERTERS.get(self.device_class)) is None
or native_unit not in converter.VALID_UNITS
or unit not in converter.VALID_UNITS
):
return native_value
return converter.convert(float(native_value), native_unit, unit)
@callback
@override
@@ -49,6 +49,7 @@ def mock_config_entry() -> MockConfigEntry:
data=VALID_CONFIG,
entry_id="test_entry_id",
version=2,
minor_version=2,
)
@@ -61,6 +62,7 @@ def mock_auth_config_entry() -> MockConfigEntry:
data=VALID_CONFIG_WITH_AUTH,
entry_id="test_entry_id",
version=2,
minor_version=2,
)
@@ -524,12 +524,12 @@
{
"id": 43,
"Text": "GPU PCIe Tx",
"Min": "0,0 KB/s",
"Min": "1,0 KB/s",
"Value": "278,6 MB/s",
"Max": "357,6 MB/s",
"SensorId": "/gpu-nvidia/0/throughput/1",
"Type": "Throughput",
"RawMin": "0,0 B/s",
"RawMin": "1000,0 B/s",
"RawValue": "292149200,0 B/s",
"RawMax": "374999000,0 B/s",
"ImageURL": "images/transparent.png",
@@ -213,7 +213,7 @@
'device_name': 'NVIDIA GeForce RTX 4080 SUPER',
'device_type': 'NVIDIA',
'max': '374999000.0',
'min': '0.0',
'min': '1000.0',
'name': 'GPU PCIe Tx Throughput',
'sensor_id': 'gpu-nvidia-0-throughput-1',
'type': 'Throughput',
@@ -25,8 +25,11 @@
'name': None,
'object_id_base': 'Core (Tctl/Tdie) Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Core (Tctl/Tdie) Temperature',
'platform': 'libre_hardware_monitor',
@@ -35,17 +38,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_amdcpu-0-temperature-2',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_amd_ryzen_7_7800x3d_core_tctl_tdie_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Core (Tctl/Tdie) Temperature',
'max_value': '69.1',
'min_value': '39.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_core_tctl_tdie_temperature',
@@ -137,8 +141,11 @@
'name': None,
'object_id_base': 'Package Power',
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Package Power',
'platform': 'libre_hardware_monitor',
@@ -153,6 +160,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_amd_ryzen_7_7800x3d_package_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Power',
'max_value': '70.1',
'min_value': '25.1',
@@ -193,8 +201,11 @@
'name': None,
'object_id_base': 'Package Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Package Temperature',
'platform': 'libre_hardware_monitor',
@@ -203,17 +214,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_amdcpu-0-temperature-3',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_amd_ryzen_7_7800x3d_package_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Temperature',
'max_value': '74.0',
'min_value': '38.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_package_temperature',
@@ -249,8 +261,11 @@
'name': None,
'object_id_base': 'VDDCR SoC Voltage',
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'VDDCR SoC Voltage',
'platform': 'libre_hardware_monitor',
@@ -265,6 +280,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_amd_ryzen_7_7800x3d_vddcr_soc_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR SoC Voltage',
'max_value': '1.306',
'min_value': '1.305',
@@ -305,8 +321,11 @@
'name': None,
'object_id_base': 'VDDCR Voltage',
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'VDDCR Voltage',
'platform': 'libre_hardware_monitor',
@@ -321,6 +340,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_amd_ryzen_7_7800x3d_vddcr_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR Voltage',
'max_value': '1.173',
'min_value': '0.452',
@@ -361,8 +381,11 @@
'name': None,
'object_id_base': '+12V Voltage',
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': '+12V Voltage',
'platform': 'libre_hardware_monitor',
@@ -377,6 +400,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_12v_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +12V Voltage',
'max_value': '12.096',
'min_value': '12.048',
@@ -417,8 +441,11 @@
'name': None,
'object_id_base': '+5V Voltage',
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': '+5V Voltage',
'platform': 'libre_hardware_monitor',
@@ -433,6 +460,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_5v_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +5V Voltage',
'max_value': '5.050',
'min_value': '5.020',
@@ -529,8 +557,11 @@
'name': None,
'object_id_base': 'CPU Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'CPU Temperature',
'platform': 'libre_hardware_monitor',
@@ -539,17 +570,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_lpc-nct6687d-0-temperature-0',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_cpu_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) CPU Temperature',
'max_value': '68.0',
'min_value': '39.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_cpu_temperature',
@@ -696,8 +728,11 @@
'name': None,
'object_id_base': 'System Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'System Temperature',
'platform': 'libre_hardware_monitor',
@@ -706,17 +741,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_lpc-nct6687d-0-temperature-1',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_system_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) System Temperature',
'max_value': '46.5',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_system_temperature',
@@ -752,8 +788,11 @@
'name': None,
'object_id_base': 'Vcore Voltage',
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'Vcore Voltage',
'platform': 'libre_hardware_monitor',
@@ -768,6 +807,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_vcore_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) Vcore Voltage',
'max_value': '1.318',
'min_value': '1.310',
@@ -808,8 +848,11 @@
'name': None,
'object_id_base': 'GPU Core Clock',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.FREQUENCY: 'frequency'>,
'original_icon': None,
'original_name': 'GPU Core Clock',
'platform': 'libre_hardware_monitor',
@@ -824,6 +867,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_core_clock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Clock',
'max_value': '2805.0',
'min_value': '210.0',
@@ -920,8 +964,11 @@
'name': None,
'object_id_base': 'GPU Core Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'GPU Core Temperature',
'platform': 'libre_hardware_monitor',
@@ -930,17 +977,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_gpu-nvidia-0-temperature-0',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_core_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Temperature',
'max_value': '37.0',
'min_value': '25.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_core_temperature',
@@ -1088,8 +1136,11 @@
'name': None,
'object_id_base': 'GPU Hot Spot Temperature',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'GPU Hot Spot Temperature',
'platform': 'libre_hardware_monitor',
@@ -1098,17 +1149,18 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_gpu-nvidia-0-temperature-2',
'unit_of_measurement': '°C',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_hot_spot_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Hot Spot Temperature',
'max_value': '43.3',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_hot_spot_temperature',
@@ -1144,8 +1196,11 @@
'name': None,
'object_id_base': 'GPU Memory Clock',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.FREQUENCY: 'frequency'>,
'original_icon': None,
'original_name': 'GPU Memory Clock',
'platform': 'libre_hardware_monitor',
@@ -1160,6 +1215,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_memory_clock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Memory Clock',
'max_value': '11502.0',
'min_value': '405.0',
@@ -1256,8 +1312,11 @@
'name': None,
'object_id_base': 'GPU Package Power',
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'GPU Package Power',
'platform': 'libre_hardware_monitor',
@@ -1272,6 +1331,7 @@
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_package_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Package Power',
'max_value': '66.6',
'min_value': '4.1',
@@ -1312,8 +1372,14 @@
'name': None,
'object_id_base': 'GPU PCIe Tx Throughput',
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfDataRate.KIBIBYTES_PER_SECOND: 'KiB/s'>,
}),
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.DATA_RATE: 'data_rate'>,
'original_icon': None,
'original_name': 'GPU PCIe Tx Throughput',
'platform': 'libre_hardware_monitor',
@@ -1322,24 +1388,25 @@
'supported_features': 0,
'translation_key': None,
'unique_id': 'test_entry_id_gpu-nvidia-0-throughput-1',
'unit_of_measurement': 'KB/s',
'unit_of_measurement': <UnitOfDataRate.KIBIBYTES_PER_SECOND: 'KiB/s'>,
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_pcie_tx_throughput-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'data_rate',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU PCIe Tx Throughput',
'max_value': '366210.0',
'min_value': '0.0',
'max_value': 366209.9609375,
'min_value': 0.9765625,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'KB/s',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDataRate.KIBIBYTES_PER_SECOND: 'KiB/s'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_pcie_tx_throughput',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '285302.0',
'state': '285301.953125',
})
# ---
# name: test_sensors_are_created[sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_video_engine_load-entry]
@@ -1402,6 +1469,7 @@
list([
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +12V Voltage',
'max_value': '12.096',
'min_value': '12.048',
@@ -1417,6 +1485,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +5V Voltage',
'max_value': '5.050',
'min_value': '5.020',
@@ -1432,6 +1501,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) Vcore Voltage',
'max_value': '1.318',
'min_value': '1.310',
@@ -1447,11 +1517,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) CPU Temperature',
'max_value': '68.0',
'min_value': '39.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_cpu_temperature',
@@ -1462,11 +1533,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) System Temperature',
'max_value': '46.5',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_system_temperature',
@@ -1521,6 +1593,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR Voltage',
'max_value': '1.173',
'min_value': '0.452',
@@ -1536,6 +1609,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR SoC Voltage',
'max_value': '1.306',
'min_value': '1.305',
@@ -1551,6 +1625,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Power',
'max_value': '70.1',
'min_value': '25.1',
@@ -1566,11 +1641,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Core (Tctl/Tdie) Temperature',
'max_value': '69.1',
'min_value': '39.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_core_tctl_tdie_temperature',
@@ -1581,11 +1657,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Temperature',
'max_value': '74.0',
'min_value': '38.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_package_temperature',
@@ -1611,6 +1688,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Package Power',
'max_value': '66.6',
'min_value': '4.1',
@@ -1626,6 +1704,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Clock',
'max_value': '2805.0',
'min_value': '210.0',
@@ -1641,6 +1720,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Memory Clock',
'max_value': '11502.0',
'min_value': '405.0',
@@ -1656,11 +1736,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Temperature',
'max_value': '37.0',
'min_value': '25.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_core_temperature',
@@ -1671,11 +1752,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Hot Spot Temperature',
'max_value': '43.3',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_hot_spot_temperature',
@@ -1761,18 +1843,19 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'data_rate',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU PCIe Tx Throughput',
'max_value': '366210.0',
'min_value': '0.0',
'max_value': 366209.9609375,
'min_value': 0.9765625,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'KB/s',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDataRate.KIBIBYTES_PER_SECOND: 'KiB/s'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_pcie_tx_throughput',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '285302.0',
'state': '285301.953125',
}),
])
# ---
@@ -1780,6 +1863,7 @@
list([
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +12V Voltage',
'max_value': '12.096',
'min_value': '12.048',
@@ -1795,6 +1879,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) +5V Voltage',
'max_value': '5.050',
'min_value': '5.020',
@@ -1810,6 +1895,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) Vcore Voltage',
'max_value': '1.318',
'min_value': '1.310',
@@ -1825,11 +1911,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) CPU Temperature',
'max_value': '68.0',
'min_value': '39.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_cpu_temperature',
@@ -1840,11 +1927,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] MSI MAG B650M MORTAR WIFI (MS-7D76) System Temperature',
'max_value': '46.5',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_msi_mag_b650m_mortar_wifi_ms_7d76_system_temperature',
@@ -1899,6 +1987,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR Voltage',
'max_value': '1.173',
'min_value': '0.452',
@@ -1914,6 +2003,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'voltage',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D VDDCR SoC Voltage',
'max_value': '1.306',
'min_value': '1.305',
@@ -1929,6 +2019,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Power',
'max_value': '70.1',
'min_value': '25.1',
@@ -1944,11 +2035,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Core (Tctl/Tdie) Temperature',
'max_value': '69.1',
'min_value': '39.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_core_tctl_tdie_temperature',
@@ -1959,11 +2051,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] AMD Ryzen 7 7800X3D Package Temperature',
'max_value': '74.0',
'min_value': '38.4',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_amd_ryzen_7_7800x3d_package_temperature',
@@ -1989,6 +2082,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'power',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Package Power',
'max_value': '66.6',
'min_value': '4.1',
@@ -2004,6 +2098,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Clock',
'max_value': '2805.0',
'min_value': '210.0',
@@ -2019,6 +2114,7 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'frequency',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Memory Clock',
'max_value': '11502.0',
'min_value': '405.0',
@@ -2034,11 +2130,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Core Temperature',
'max_value': '37.0',
'min_value': '25.0',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_core_temperature',
@@ -2049,11 +2146,12 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU Hot Spot Temperature',
'max_value': '43.3',
'min_value': '32.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '°C',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_hot_spot_temperature',
@@ -2139,18 +2237,19 @@
}),
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'data_rate',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: '[GAMING-PC] NVIDIA GeForce RTX 4080 SUPER GPU PCIe Tx Throughput',
'max_value': '366210.0',
'min_value': '0.0',
'max_value': 366209.9609375,
'min_value': 0.9765625,
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'KB/s',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDataRate.KIBIBYTES_PER_SECOND: 'KiB/s'>,
}),
'context': <ANY>,
'entity_id': 'sensor.gaming_pc_nvidia_geforce_rtx_4080_super_gpu_pcie_tx_throughput',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '285302.0',
'state': '285301.953125',
}),
])
# ---
@@ -10,8 +10,13 @@ import pytest
from homeassistant.components.libre_hardware_monitor.const import (
DEFAULT_SCAN_INTERVAL,
DOMAIN,
LEGACY_THROUGHPUT_UNIT,
)
from homeassistant.components.libre_hardware_monitor.recorder import (
async_custom_equivalent_units,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, UnitOfDataRate
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -102,6 +107,69 @@ async def test_migration_to_unique_ids(
legacy_config_entry_v1.entry_id
)
assert updated_config_entry.version == 2
assert updated_config_entry.minor_version == 2
@pytest.mark.parametrize(
("version", "minor_version", "unique_id_prefix"),
[
pytest.param(1, 1, "lhm-", id="from_v1"),
pytest.param(2, 1, "test_entry_id_", id="from_v2_minor_1"),
],
)
@pytest.mark.usefixtures("mock_lhm_client", "recorder_mock")
async def test_migration_to_sensor_device_classes(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
version: int,
minor_version: int,
unique_id_prefix: str,
) -> None:
"""Test that throughput sensor units are updated from every legacy version."""
legacy_config_entry = MockConfigEntry(
domain=DOMAIN,
title="192.168.0.20:8085",
data=VALID_CONFIG,
entry_id="test_entry_id",
version=version,
minor_version=minor_version,
)
legacy_config_entry.add_to_hass(hass)
# Set up throughput sensor with old unit
object_id = "nvidia_geforce_rtx_4080_gpu_pcie_tx_throughput"
entity_registry.async_get_or_create(
"sensor",
DOMAIN,
f"{unique_id_prefix}gpu-nvidia-0-throughput-1",
suggested_object_id=object_id,
config_entry=legacy_config_entry,
unit_of_measurement=LEGACY_THROUGHPUT_UNIT,
)
await init_integration(hass, legacy_config_entry)
entity_entry = entity_registry.async_get(f"sensor.{object_id}")
assert entity_entry.unit_of_measurement == UnitOfDataRate.KIBIBYTES_PER_SECOND
# the entity keeps reporting the migrated unit once it is set up
state = hass.states.get(f"sensor.{object_id}")
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == (
UnitOfDataRate.KIBIBYTES_PER_SECOND
)
custom_equivalent_units = async_custom_equivalent_units(hass)
assert custom_equivalent_units == {
f"sensor.{object_id}": {
LEGACY_THROUGHPUT_UNIT: UnitOfDataRate.KIBIBYTES_PER_SECOND
}
}
updated_config_entry = hass.config_entries.async_get_entry(
legacy_config_entry.entry_id
)
assert updated_config_entry.version == 2
assert updated_config_entry.minor_version == 2
@pytest.mark.usefixtures("mock_deprecated_lhm_client")
@@ -24,8 +24,31 @@ from homeassistant.components.libre_hardware_monitor.const import (
DEFAULT_SCAN_INTERVAL,
DOMAIN,
)
from homeassistant.components.libre_hardware_monitor.sensor import (
STATE_MAX_VALUE,
STATE_MIN_VALUE,
)
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
PERCENTAGE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfConductivity,
UnitOfDataRate,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfFrequency,
UnitOfInformation,
UnitOfPower,
UnitOfSoundPressure,
UnitOfTemperature,
UnitOfTime,
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntry
@@ -48,6 +71,218 @@ async def test_sensors_are_created(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("sensor_type", "lhm_unit", "expected_device_class", "expected_unit"),
[
pytest.param(
SensorType.VOLTAGE,
"V",
SensorDeviceClass.VOLTAGE,
UnitOfElectricPotential.VOLT,
id="voltage",
),
pytest.param(
SensorType.CURRENT,
"A",
SensorDeviceClass.CURRENT,
UnitOfElectricCurrent.AMPERE,
id="current",
),
pytest.param(
SensorType.POWER,
"W",
SensorDeviceClass.POWER,
UnitOfPower.WATT,
id="power",
),
pytest.param(
SensorType.CLOCK,
"MHz",
SensorDeviceClass.FREQUENCY,
UnitOfFrequency.MEGAHERTZ,
id="clock",
),
pytest.param(
SensorType.FREQUENCY,
"Hz",
SensorDeviceClass.FREQUENCY,
UnitOfFrequency.HERTZ,
id="frequency",
),
pytest.param(
SensorType.TEMPERATURE,
"°C",
SensorDeviceClass.TEMPERATURE,
UnitOfTemperature.CELSIUS,
id="temperature",
),
pytest.param(
SensorType.FLOW,
"L/h",
SensorDeviceClass.VOLUME_FLOW_RATE,
UnitOfVolumeFlowRate.LITERS_PER_HOUR,
id="flow",
),
pytest.param(
SensorType.DATA,
"GB",
SensorDeviceClass.DATA_SIZE,
UnitOfInformation.GIGABYTES,
id="data",
),
pytest.param(
SensorType.SMALL_DATA,
"MB",
SensorDeviceClass.DATA_SIZE,
UnitOfInformation.MEGABYTES,
id="small_data",
),
pytest.param(
SensorType.THROUGHPUT,
"B/s",
SensorDeviceClass.DATA_RATE,
UnitOfDataRate.KIBIBYTES_PER_SECOND,
id="throughput",
),
pytest.param(
SensorType.TIMESPAN,
"s",
SensorDeviceClass.DURATION,
UnitOfTime.SECONDS,
id="timespan",
),
pytest.param(
SensorType.ENERGY,
"mWh",
SensorDeviceClass.ENERGY_STORAGE,
UnitOfEnergy.MILLIWATT_HOUR,
id="energy",
),
pytest.param(
SensorType.NOISE,
"dBA",
SensorDeviceClass.SOUND_PRESSURE,
UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
id="noise",
),
pytest.param(
SensorType.CONDUCTIVITY,
# LHM sends the micro sign (U+00B5), HA normalizes it to greek mu (U+03BC)
"\u00b5S/cm",
SensorDeviceClass.CONDUCTIVITY,
UnitOfConductivity.MICROSIEMENS_PER_CM,
id="conductivity",
),
pytest.param(
SensorType.HUMIDITY,
"%",
SensorDeviceClass.HUMIDITY,
PERCENTAGE,
id="humidity",
),
pytest.param(SensorType.FACTOR, None, None, None, id="factor_unmapped"),
pytest.param(None, None, None, None, id="unknown_type"),
],
)
async def test_sensor_device_class_mapping(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_lhm_client: AsyncMock,
mock_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
sensor_type: SensorType | None,
lhm_unit: str | None,
expected_device_class: SensorDeviceClass | None,
expected_unit: str | None,
) -> None:
"""Test every LHM sensor type gets the expected device class and unit."""
sensor_id = "gpu-nvidia-0-test-0"
mock_lhm_client.get_data.return_value = replace(
mock_lhm_client.get_data.return_value,
sensor_data=MappingProxyType(
{
sensor_id: LibreHardwareMonitorSensorData(
name="Test",
value="42.0",
type=sensor_type,
min="40.0",
max="44.0",
unit=lhm_unit,
device_id="gpu-nvidia-0",
device_name="NVIDIA GeForce RTX 4080 SUPER",
device_type="NVIDIA",
sensor_id=sensor_id,
)
}
),
)
await init_integration(hass, mock_config_entry)
entity_id = entity_registry.async_get_entity_id(
"sensor", DOMAIN, f"{mock_config_entry.entry_id}_{sensor_id}"
)
assert entity_id
state = hass.states.get(entity_id)
assert state
assert state.attributes.get(ATTR_DEVICE_CLASS) == expected_device_class
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == expected_unit
async def test_min_max_follow_selected_conductivity_unit(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_lhm_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test min and max are converted from the micro sign spelling LHM reports."""
sensor_id = "gpu-nvidia-0-conductivity-0"
mock_lhm_client.get_data.return_value = replace(
mock_lhm_client.get_data.return_value,
sensor_data=MappingProxyType(
{
sensor_id: LibreHardwareMonitorSensorData(
name="Coolant",
value="42.0",
type=SensorType.CONDUCTIVITY,
min="40.0",
max="44.0",
unit="\u00b5S/cm",
device_id="gpu-nvidia-0",
device_name="NVIDIA GeForce RTX 4080 SUPER",
device_type="NVIDIA",
sensor_id=sensor_id,
)
}
),
)
await init_integration(hass, mock_config_entry)
entity_id = entity_registry.async_get_entity_id(
"sensor", DOMAIN, f"{mock_config_entry.entry_id}_{sensor_id}"
)
assert entity_id
entity_registry.async_update_entity_options(
entity_id,
"sensor",
{"unit_of_measurement": UnitOfConductivity.MILLISIEMENS_PER_CM},
)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == (
UnitOfConductivity.MILLISIEMENS_PER_CM
)
assert float(state.state) == pytest.approx(0.042)
assert state.attributes[STATE_MIN_VALUE] == pytest.approx(0.04)
assert state.attributes[STATE_MAX_VALUE] == pytest.approx(0.044)
@pytest.mark.parametrize(
"error", [LibreHardwareMonitorConnectionError, LibreHardwareMonitorNoDevicesError]
)
@@ -141,8 +376,8 @@ async def test_sensor_invalid_auth_during_startup(
(
"gaming_pc_nvidia_geforce_rtx_4080_super_gpu_pcie_tx_throughput",
"gpu-nvidia-0-throughput-1",
"792150000.0",
"773584.0",
"811161600.0",
"792150.0",
),
],
)