mirror of
https://github.com/home-assistant/core.git
synced 2025-09-01 10:51:47 +02:00
Fix
This commit is contained in:
@@ -72,11 +72,11 @@ def get_process(entity: SystemMonitorSensor) -> bool:
|
||||
class SysMonitorBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Describes System Monitor binary sensor entities."""
|
||||
|
||||
value_fn: Callable[[SystemMonitorSensor], bool]
|
||||
value_fn: Callable[[SystemMonitorSensor], bool | None]
|
||||
add_to_update: Callable[[SystemMonitorSensor], tuple[str, str]]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = (
|
||||
PROCESS_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = (
|
||||
SysMonitorBinarySensorEntityDescription(
|
||||
key="binary_process",
|
||||
translation_key="process",
|
||||
@@ -87,6 +87,17 @@ SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
BINARY_SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = (
|
||||
SysMonitorBinarySensorEntityDescription(
|
||||
key="battery_plugged",
|
||||
value_fn=lambda entity: entity.coordinator.data.battery.power_plugged
|
||||
if entity.coordinator.data.battery
|
||||
else None,
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
add_to_update=lambda entity: ("battery", ""),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -96,18 +107,30 @@ async def async_setup_entry(
|
||||
"""Set up System Monitor binary sensors based on a config entry."""
|
||||
coordinator = entry.runtime_data.coordinator
|
||||
|
||||
async_add_entities(
|
||||
entities: list[SystemMonitorSensor] = []
|
||||
|
||||
entities.extend(
|
||||
SystemMonitorSensor(
|
||||
coordinator,
|
||||
sensor_description,
|
||||
entry.entry_id,
|
||||
argument,
|
||||
)
|
||||
for sensor_description in SENSOR_TYPES
|
||||
for sensor_description in PROCESS_TYPES
|
||||
for argument in entry.options.get(BINARY_SENSOR_DOMAIN, {}).get(
|
||||
CONF_PROCESS, []
|
||||
)
|
||||
)
|
||||
entities.extend(
|
||||
SystemMonitorSensor(
|
||||
coordinator,
|
||||
sensor_description,
|
||||
entry.entry_id,
|
||||
"",
|
||||
)
|
||||
for sensor_description in BINARY_SENSOR_TYPES
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class SystemMonitorSensor(
|
||||
|
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"battery": {
|
||||
"default": "mdi:battery"
|
||||
"battery_left": {
|
||||
"default": "mdi:battery-clock"
|
||||
},
|
||||
"disk_free": {
|
||||
"default": "mdi:harddisk"
|
||||
|
@@ -14,6 +14,8 @@ import sys
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
|
||||
from psutil._common import POWER_TIME_UNKNOWN, POWER_TIME_UNLIMITED
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
SensorDeviceClass,
|
||||
@@ -28,6 +30,7 @@ from homeassistant.const import (
|
||||
UnitOfDataRate,
|
||||
UnitOfInformation,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -55,6 +58,8 @@ SENSOR_TYPE_MANDATORY_ARG = 4
|
||||
|
||||
SIGNAL_SYSTEMMONITOR_UPDATE = "systemmonitor_update"
|
||||
|
||||
BATTERY_REMAIN_UNKNOWNS = (POWER_TIME_UNKNOWN, POWER_TIME_UNLIMITED)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]:
|
||||
@@ -119,6 +124,14 @@ def get_ip_address(
|
||||
return None
|
||||
|
||||
|
||||
def battery_seconds_left(entity: SystemMonitorSensor) -> int | None:
|
||||
"""Return remaining battery time in seconds."""
|
||||
battery = entity.coordinator.data.battery
|
||||
if not battery or battery.secsleft in BATTERY_REMAIN_UNKNOWNS:
|
||||
return None
|
||||
return battery.secsleft
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes System Monitor sensor entities."""
|
||||
@@ -133,7 +146,6 @@ class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
||||
SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
||||
"battery": SysMonitorSensorEntityDescription(
|
||||
key="battery",
|
||||
translation_key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -143,6 +155,17 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
||||
none_is_unavailable=True,
|
||||
add_to_update=lambda entity: ("battery", ""),
|
||||
),
|
||||
"battery_left": SysMonitorSensorEntityDescription(
|
||||
key="battery_left",
|
||||
translation_key="battery_left",
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=battery_seconds_left,
|
||||
none_is_unavailable=True,
|
||||
add_to_update=lambda entity: ("battery", ""),
|
||||
suggested_unit_of_measurement=UnitOfTime.MINUTES,
|
||||
),
|
||||
"disk_free": SysMonitorSensorEntityDescription(
|
||||
key="disk_free",
|
||||
translation_key="disk_free",
|
||||
@@ -190,7 +213,7 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
||||
"fan_rpm": SysMonitorSensorEntityDescription(
|
||||
key="fan_rpm",
|
||||
translation_key="fan_rpm",
|
||||
placeholder="name",
|
||||
placeholder="fan_name",
|
||||
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda entity: entity.coordinator.data.fan_rpm[entity.argument],
|
||||
@@ -590,7 +613,7 @@ async def async_setup_entry( # noqa: C901
|
||||
)
|
||||
)
|
||||
|
||||
if _type == "battery":
|
||||
if _type.startswith("battery"):
|
||||
argument = ""
|
||||
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||
entities.append(
|
||||
|
@@ -29,7 +29,7 @@
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"battery": {
|
||||
"battery_left": {
|
||||
"name": "Battery"
|
||||
},
|
||||
"disk_free": {
|
||||
@@ -42,7 +42,7 @@
|
||||
"name": "Disk usage {mount_point}"
|
||||
},
|
||||
"fan_rpm": {
|
||||
"name": "{name} fan RPM"
|
||||
"name": "{fan_name} fan RPM"
|
||||
},
|
||||
"ipv4_address": {
|
||||
"name": "IPv4 address {ip_address}"
|
||||
|
@@ -193,8 +193,7 @@ def mock_psutil(mock_process: list[MockProcess]) -> Generator:
|
||||
mock_psutil.NoSuchProcess = NoSuchProcess
|
||||
# mock_psutil.sensors_fans = Mock()
|
||||
mock_psutil.sensors_fans.return_value = {
|
||||
"fan1": [sfan("fan1", 1200)],
|
||||
"fan2": [sfan("fan2", 1300)],
|
||||
"asus": [sfan("cpu-fan", 1200), sfan("another-fan", 1300)],
|
||||
}
|
||||
mock_psutil.sensors_battery.return_value = sbattery(
|
||||
percent=93, secsleft=16628, power_plugged=False
|
||||
|
@@ -1,4 +1,13 @@
|
||||
# serializer version: 1
|
||||
# name: test_binary_sensor[System Monitor Charging - attributes]
|
||||
ReadOnlyDict({
|
||||
'device_class': 'battery_charging',
|
||||
'friendly_name': 'System Monitor Charging',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensor[System Monitor Charging - state]
|
||||
'off'
|
||||
# ---
|
||||
# name: test_binary_sensor[System Monitor Process pip - attributes]
|
||||
ReadOnlyDict({
|
||||
'device_class': 'running',
|
||||
|
@@ -8,6 +8,7 @@
|
||||
'eth1': "[snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]",
|
||||
'vethxyzxyz': "[snicaddr(family=<AddressFamily.AF_INET: 2>, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]",
|
||||
}),
|
||||
'battery': 'sbattery(percent=93, secsleft=16628, power_plugged=False)',
|
||||
'boot_time': '2024-02-24 15:00:00+00:00',
|
||||
'cpu_percent': '10.0',
|
||||
'disk_usage': dict({
|
||||
@@ -15,6 +16,10 @@
|
||||
'/home/notexist/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||
'/media/share': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||
}),
|
||||
'fan_rpm': dict({
|
||||
'another-fan': '1300',
|
||||
'cpu-fan': '1200',
|
||||
}),
|
||||
'io_counters': dict({
|
||||
'eth0': 'snetio(bytes_sent=104857600, bytes_recv=104857600, packets_sent=50, packets_recv=50, errin=0, errout=0, dropin=0, dropout=0)',
|
||||
'eth1': 'snetio(bytes_sent=209715200, bytes_recv=209715200, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)',
|
||||
@@ -69,6 +74,7 @@
|
||||
'coordinators': dict({
|
||||
'data': dict({
|
||||
'addresses': None,
|
||||
'battery': 'sbattery(percent=93, secsleft=16628, power_plugged=False)',
|
||||
'boot_time': '2024-02-24 15:00:00+00:00',
|
||||
'cpu_percent': '10.0',
|
||||
'disk_usage': dict({
|
||||
@@ -76,6 +82,10 @@
|
||||
'/home/notexist/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||
'/media/share': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)',
|
||||
}),
|
||||
'fan_rpm': dict({
|
||||
'another-fan': '1300',
|
||||
'cpu-fan': '1200',
|
||||
}),
|
||||
'io_counters': None,
|
||||
'load': '(1, 2, 3)',
|
||||
'memory': 'VirtualMemory(total=104857600, available=41943040, percent=40.0, used=62914560, free=31457280)',
|
||||
|
@@ -1,4 +1,15 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensor[System Monitor Battery - attributes]
|
||||
ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'System Monitor Battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Battery - state]
|
||||
'93'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Disk free / - attributes]
|
||||
ReadOnlyDict({
|
||||
'device_class': 'data_size',
|
||||
@@ -73,6 +84,17 @@
|
||||
# name: test_sensor[System Monitor Disk use /media/share - state]
|
||||
'300.0'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Duration - attributes]
|
||||
ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
'friendly_name': 'System Monitor Duration',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Duration - state]
|
||||
'16628'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor IPv4 address eth0 - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor IPv4 address eth0',
|
||||
@@ -114,16 +136,6 @@
|
||||
# name: test_sensor[System Monitor Last boot - state]
|
||||
'2024-02-24T15:00:00+00:00'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (15 min) - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor Load (15 min)',
|
||||
'icon': 'mdi:cpu-64-bit',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (15 min) - state]
|
||||
'3'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (1 min) - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor Load (1 min)',
|
||||
@@ -134,6 +146,16 @@
|
||||
# name: test_sensor[System Monitor Load (1 min) - state]
|
||||
'1'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (15 min) - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor Load (15 min)',
|
||||
'icon': 'mdi:cpu-64-bit',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (15 min) - state]
|
||||
'3'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor Load (5 min) - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor Load (5 min)',
|
||||
@@ -354,3 +376,23 @@
|
||||
# name: test_sensor[System Monitor Swap use - state]
|
||||
'60.0'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor another-fan fan RPM - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor another-fan fan RPM',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'rpm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor another-fan fan RPM - state]
|
||||
'1300'
|
||||
# ---
|
||||
# name: test_sensor[System Monitor cpu-fan fan RPM - attributes]
|
||||
ReadOnlyDict({
|
||||
'friendly_name': 'System Monitor cpu-fan fan RPM',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'rpm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[System Monitor cpu-fan fan RPM - state]
|
||||
'1200'
|
||||
# ---
|
||||
|
@@ -453,7 +453,7 @@ async def test_remove_obsolete_entities(
|
||||
mock_added_config_entry.entry_id
|
||||
)
|
||||
)
|
||||
== 37
|
||||
== 42
|
||||
)
|
||||
|
||||
entity_registry.async_update_entity(
|
||||
@@ -494,7 +494,7 @@ async def test_remove_obsolete_entities(
|
||||
mock_added_config_entry.entry_id
|
||||
)
|
||||
)
|
||||
== 38
|
||||
== 43
|
||||
)
|
||||
|
||||
assert (
|
||||
|
Reference in New Issue
Block a user