1 """Support for System Bridge sensors."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from datetime
import UTC, datetime, timedelta
8 from typing
import Final, cast
10 from systembridgemodels.modules.cpu
import PerCPU
11 from systembridgemodels.modules.displays
import Display
12 from systembridgemodels.modules.gpus
import GPU
17 SensorEntityDescription,
24 REVOLUTIONS_PER_MINUTE,
25 UnitOfElectricPotential,
36 from .const
import DOMAIN
37 from .coordinator
import SystemBridgeDataUpdateCoordinator
38 from .data
import SystemBridgeData
39 from .entity
import SystemBridgeEntity
41 ATTR_AVAILABLE: Final =
"available"
42 ATTR_FILESYSTEM: Final =
"filesystem"
43 ATTR_MOUNT: Final =
"mount"
44 ATTR_SIZE: Final =
"size"
45 ATTR_TYPE: Final =
"type"
46 ATTR_USED: Final =
"used"
51 @dataclass(frozen=True)
53 """Class describing System Bridge sensor entities."""
55 value: Callable = round
59 """Return the battery time remaining."""
60 if (battery_time := data.battery.time_remaining)
is not None:
61 return dt_util.utcnow() +
timedelta(seconds=battery_time)
65 def cpu_speed(data: SystemBridgeData) -> float |
None:
66 """Return the CPU speed."""
67 if (cpu_frequency := data.cpu.frequency)
is not None and (
70 return round(cpu_frequency.current / 1000, 2)
75 """Wrap a function to ensure per CPU data is available."""
77 def wrapper(data: SystemBridgeData, index: int) -> float |
None:
78 """Wrap a function to ensure per CPU data is available."""
79 if data.cpu.per_cpu
is not None and index < len(data.cpu.per_cpu):
80 return func(data.cpu.per_cpu[index])
88 """Return CPU power per CPU."""
94 """Return CPU usage per CPU."""
99 """Wrap a function to ensure a Display is available."""
101 def wrapper(data: SystemBridgeData, index: int) -> Display |
None:
102 """Wrap a function to ensure a Display is available."""
103 if index < len(data.displays):
104 return func(data.displays[index])
112 """Return the Display resolution horizontal."""
113 return display.resolution_horizontal
118 """Return the Display resolution vertical."""
119 return display.resolution_vertical
124 """Return the Display refresh rate."""
125 return display.refresh_rate
129 """Wrap a function to ensure a GPU is available."""
131 def wrapper(data: SystemBridgeData, index: int) -> GPU |
None:
132 """Wrap a function to ensure a GPU is available."""
133 if index < len(data.gpus):
134 return func(data.gpus[index])
142 """Return the GPU core clock speed."""
143 return gpu.core_clock
148 """Return the GPU fan speed."""
154 """Return the GPU memory clock speed."""
155 return gpu.memory_clock
160 """Return the free GPU memory."""
161 return gpu.memory_free
166 """Return the used GPU memory."""
167 return gpu.memory_used
172 """Return the used GPU memory percentage."""
173 if (gpu.memory_used)
is not None and (gpu.memory_total)
is not None:
174 return round(gpu.memory_used / gpu.memory_total * 100, 2)
180 """Return the GPU power usage."""
181 return gpu.power_usage
186 """Return the GPU temperature."""
187 return gpu.temperature
192 """Return the GPU usage percentage."""
197 """Return the free memory."""
198 if (virtual := data.memory.virtual)
is not None and (
201 return round(free / 1000**3, 2)
206 """Return the used memory."""
207 if (virtual := data.memory.virtual)
is not None and (
210 return round(used / 1000**3, 2)
215 data: SystemBridgeData,
217 partition_index: int,
219 """Return the used memory."""
221 (devices := data.disks.devices)
is not None
222 and device_index < len(devices)
223 and (partitions := devices[device_index].partitions)
is not None
224 and partition_index < len(partitions)
225 and (usage := partitions[partition_index].usage)
is not None
231 BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
234 translation_key=
"boot_time",
235 device_class=SensorDeviceClass.TIMESTAMP,
237 value=
lambda data: datetime.fromtimestamp(data.system.boot_time, tz=UTC),
240 key=
"cpu_power_package",
241 translation_key=
"cpu_power_package",
242 native_unit_of_measurement=UnitOfPower.WATT,
243 state_class=SensorStateClass.MEASUREMENT,
244 suggested_display_precision=2,
246 value=
lambda data: data.cpu.power,
250 translation_key=
"cpu_speed",
251 state_class=SensorStateClass.MEASUREMENT,
252 native_unit_of_measurement=UnitOfFrequency.GIGAHERTZ,
253 device_class=SensorDeviceClass.FREQUENCY,
254 icon=
"mdi:speedometer",
258 key=
"cpu_temperature",
259 translation_key=
"cpu_temperature",
260 entity_registry_enabled_default=
False,
261 device_class=SensorDeviceClass.TEMPERATURE,
262 state_class=SensorStateClass.MEASUREMENT,
263 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
264 value=
lambda data: data.cpu.temperature,
268 translation_key=
"cpu_voltage",
269 entity_registry_enabled_default=
False,
270 device_class=SensorDeviceClass.VOLTAGE,
271 state_class=SensorStateClass.MEASUREMENT,
272 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
273 value=
lambda data: data.cpu.voltage,
277 translation_key=
"kernel",
279 value=
lambda data: data.system.platform,
283 translation_key=
"memory_free",
284 state_class=SensorStateClass.MEASUREMENT,
285 native_unit_of_measurement=UnitOfInformation.GIGABYTES,
286 device_class=SensorDeviceClass.DATA_SIZE,
291 key=
"memory_used_percentage",
292 state_class=SensorStateClass.MEASUREMENT,
293 native_unit_of_measurement=PERCENTAGE,
295 value=
lambda data: data.memory.virtual.percent,
299 translation_key=
"memory_used",
300 entity_registry_enabled_default=
False,
301 state_class=SensorStateClass.MEASUREMENT,
302 native_unit_of_measurement=UnitOfInformation.GIGABYTES,
303 device_class=SensorDeviceClass.DATA_SIZE,
309 translation_key=
"os",
311 value=
lambda data: f
"{data.system.platform} {data.system.platform_version}",
314 key=
"processes_count",
315 translation_key=
"processes",
316 state_class=SensorStateClass.MEASUREMENT,
318 value=
lambda data: len(data.processes),
321 key=
"processes_load",
322 translation_key=
"load",
323 state_class=SensorStateClass.MEASUREMENT,
324 native_unit_of_measurement=PERCENTAGE,
326 value=
lambda data: data.cpu.usage,
330 translation_key=
"version",
332 value=
lambda data: data.system.version,
335 key=
"version_latest",
336 translation_key=
"version_latest",
338 value=
lambda data: data.system.version_latest,
342 BATTERY_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
345 device_class=SensorDeviceClass.BATTERY,
346 state_class=SensorStateClass.MEASUREMENT,
347 native_unit_of_measurement=PERCENTAGE,
348 value=
lambda data: data.battery.percentage,
351 key=
"battery_time_remaining",
352 translation_key=
"battery_time_remaining",
353 device_class=SensorDeviceClass.TIMESTAMP,
354 value=battery_time_remaining,
362 async_add_entities: AddEntitiesCallback,
364 """Set up System Bridge sensor based on a config entry."""
365 coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
369 for description
in BASE_SENSOR_TYPES
372 for index_device, device
in enumerate(coordinator.data.disks.devices):
373 if device.partitions
is None:
380 key=f
"filesystem_{partition.mount_point.replace(':', '')}",
381 name=f
"{partition.mount_point} space used",
382 state_class=SensorStateClass.MEASUREMENT,
383 native_unit_of_measurement=PERCENTAGE,
391 entry.data[CONF_PORT],
393 for index_partition, partition
in enumerate(device.partitions)
397 coordinator.data.battery
398 and coordinator.data.battery.percentage
399 and coordinator.data.battery.percentage > -1
403 for description
in BATTERY_SENSOR_TYPES
410 key=
"displays_connected",
411 translation_key=
"displays_connected",
412 state_class=SensorStateClass.MEASUREMENT,
414 value=
lambda data: len(data.displays)
if data.displays
else None,
416 entry.data[CONF_PORT],
420 if coordinator.data.displays
is not None:
421 for index, display
in enumerate(coordinator.data.displays):
427 key=f
"display_{display.id}_resolution_x",
428 name=f
"Display {display.id} resolution x",
429 state_class=SensorStateClass.MEASUREMENT,
430 native_unit_of_measurement=PIXELS,
436 entry.data[CONF_PORT],
441 key=f
"display_{display.id}_resolution_y",
442 name=f
"Display {display.id} resolution y",
443 state_class=SensorStateClass.MEASUREMENT,
444 native_unit_of_measurement=PIXELS,
450 entry.data[CONF_PORT],
455 key=f
"display_{display.id}_refresh_rate",
456 name=f
"Display {display.id} refresh rate",
457 state_class=SensorStateClass.MEASUREMENT,
458 native_unit_of_measurement=UnitOfFrequency.HERTZ,
459 device_class=SensorDeviceClass.FREQUENCY,
463 entry.data[CONF_PORT],
467 for index, gpu
in enumerate(coordinator.data.gpus):
473 key=f
"gpu_{gpu.id}_core_clock_speed",
474 name=f
"{gpu.name} clock speed",
475 entity_registry_enabled_default=
False,
476 state_class=SensorStateClass.MEASUREMENT,
477 native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
478 device_class=SensorDeviceClass.FREQUENCY,
479 icon=
"mdi:speedometer",
482 entry.data[CONF_PORT],
487 key=f
"gpu_{gpu.id}_memory_clock_speed",
488 name=f
"{gpu.name} memory clock speed",
489 entity_registry_enabled_default=
False,
490 state_class=SensorStateClass.MEASUREMENT,
491 native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
492 device_class=SensorDeviceClass.FREQUENCY,
493 icon=
"mdi:speedometer",
496 entry.data[CONF_PORT],
501 key=f
"gpu_{gpu.id}_memory_free",
502 name=f
"{gpu.name} memory free",
503 state_class=SensorStateClass.MEASUREMENT,
504 native_unit_of_measurement=UnitOfInformation.MEGABYTES,
505 device_class=SensorDeviceClass.DATA_SIZE,
509 entry.data[CONF_PORT],
514 key=f
"gpu_{gpu.id}_memory_used_percentage",
515 name=f
"{gpu.name} memory used %",
516 state_class=SensorStateClass.MEASUREMENT,
517 native_unit_of_measurement=PERCENTAGE,
521 entry.data[CONF_PORT],
526 key=f
"gpu_{gpu.id}_memory_used",
527 name=f
"{gpu.name} memory used",
528 entity_registry_enabled_default=
False,
529 state_class=SensorStateClass.MEASUREMENT,
530 native_unit_of_measurement=UnitOfInformation.MEGABYTES,
531 device_class=SensorDeviceClass.DATA_SIZE,
535 entry.data[CONF_PORT],
540 key=f
"gpu_{gpu.id}_fan_speed",
541 name=f
"{gpu.name} fan speed",
542 entity_registry_enabled_default=
False,
543 state_class=SensorStateClass.MEASUREMENT,
544 native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
548 entry.data[CONF_PORT],
553 key=f
"gpu_{gpu.id}_power_usage",
554 name=f
"{gpu.name} power usage",
555 entity_registry_enabled_default=
False,
556 device_class=SensorDeviceClass.POWER,
557 state_class=SensorStateClass.MEASUREMENT,
558 native_unit_of_measurement=UnitOfPower.WATT,
561 entry.data[CONF_PORT],
566 key=f
"gpu_{gpu.id}_temperature",
567 name=f
"{gpu.name} temperature",
568 entity_registry_enabled_default=
False,
569 device_class=SensorDeviceClass.TEMPERATURE,
570 state_class=SensorStateClass.MEASUREMENT,
571 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
574 entry.data[CONF_PORT],
579 key=f
"gpu_{gpu.id}_usage_percentage",
580 name=f
"{gpu.name} usage %",
581 state_class=SensorStateClass.MEASUREMENT,
582 native_unit_of_measurement=PERCENTAGE,
586 entry.data[CONF_PORT],
591 if coordinator.data.cpu.per_cpu
is not None:
592 for cpu
in coordinator.data.cpu.per_cpu:
598 key=f
"processes_load_cpu_{cpu.id}",
599 name=f
"Load CPU {cpu.id}",
600 entity_registry_enabled_default=
False,
601 state_class=SensorStateClass.MEASUREMENT,
602 native_unit_of_measurement=PERCENTAGE,
606 entry.data[CONF_PORT],
611 key=f
"cpu_power_core_{cpu.id}",
612 name=f
"CPU Core {cpu.id} Power",
613 entity_registry_enabled_default=
False,
614 native_unit_of_measurement=UnitOfPower.WATT,
615 state_class=SensorStateClass.MEASUREMENT,
619 entry.data[CONF_PORT],
628 """Define a System Bridge sensor."""
630 entity_description: SystemBridgeSensorEntityDescription
634 coordinator: SystemBridgeDataUpdateCoordinator,
635 description: SystemBridgeSensorEntityDescription,
645 if description.name != UNDEFINED:
650 """Return the state."""
652 return cast(StateType, self.
entity_descriptionentity_description.value(self.coordinator.data))
bool _attr_has_entity_name
None __init__(self, SystemBridgeDataUpdateCoordinator coordinator, SystemBridgeSensorEntityDescription description, int api_port)
StateType native_value(self)
float|None gpu_power_usage(GPU gpu)
Callable with_display(func)
float|None memory_used(SystemBridgeData data)
float|None memory_free(SystemBridgeData data)
float|None cpu_power_per_cpu(PerCPU per_cpu)
int|None display_resolution_vertical(Display display)
float|None cpu_speed(SystemBridgeData data)
float|None gpu_temperature(GPU gpu)
int|None display_resolution_horizontal(Display display)
float|None display_refresh_rate(Display display)
float|None gpu_fan_speed(GPU gpu)
float|None gpu_memory_clock_speed(GPU gpu)
float|None gpu_memory_free(GPU gpu)
float|None gpu_core_clock_speed(GPU gpu)
float|None gpu_usage_percentage(GPU gpu)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
datetime|None battery_time_remaining(SystemBridgeData data)
float|None gpu_memory_used_percentage(GPU gpu)
float|None partition_usage(SystemBridgeData data, int device_index, int partition_index)
Callable with_per_cpu(func)
float|None gpu_memory_used(GPU gpu)
float|None cpu_usage_per_cpu(PerCPU per_cpu)