core/homeassistant/components/prusalink/sensor.py

253 lines
9.6 KiB
Python

"""PrusaLink sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Generic, TypeVar, cast
from pyprusalink.types import JobInfo, PrinterInfo, PrinterState, PrinterStatus
from pyprusalink.types_legacy import LegacyPrinterStatus
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
UnitOfLength,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow
from homeassistant.util.variance import ignore_variance
from .const import DOMAIN
from .coordinator import PrusaLinkUpdateCoordinator
from .entity import PrusaLinkEntity
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo)
@dataclass(frozen=True)
class PrusaLinkSensorEntityDescriptionMixin(Generic[T]):
"""Mixin for required keys."""
value_fn: Callable[[T], datetime | StateType]
@dataclass(frozen=True)
class PrusaLinkSensorEntityDescription(
SensorEntityDescription, PrusaLinkSensorEntityDescriptionMixin[T], Generic[T]
):
"""Describes PrusaLink sensor entity."""
available_fn: Callable[[T], bool] = lambda _: True
SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = {
"status": (
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.state",
name=None,
value_fn=lambda data: (cast(str, data["printer"]["state"].lower())),
device_class=SensorDeviceClass.ENUM,
options=[state.value.lower() for state in PrinterState],
translation_key="printer_state",
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.temp-bed",
translation_key="heatbed_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: cast(float, data["printer"]["temp_bed"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.temp-nozzle",
translation_key="nozzle_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: cast(float, data["printer"]["temp_nozzle"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.temp-bed.target",
translation_key="heatbed_target_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: cast(float, data["printer"]["target_bed"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.temp-nozzle.target",
translation_key="nozzle_target_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: cast(float, data["printer"]["target_nozzle"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.z-height",
translation_key="z_height",
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
device_class=SensorDeviceClass.DISTANCE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: cast(float, data["printer"]["axis_z"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.print-speed",
translation_key="print_speed",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: cast(float, data["printer"]["speed"]),
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.print-flow",
translation_key="print_flow",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: cast(float, data["printer"]["flow"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.fan-hotend",
translation_key="fan_hotend",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
value_fn=lambda data: cast(float, data["printer"]["fan_hotend"]),
entity_registry_enabled_default=False,
),
PrusaLinkSensorEntityDescription[PrinterStatus](
key="printer.telemetry.fan-print",
translation_key="fan_print",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
value_fn=lambda data: cast(float, data["printer"]["fan_print"]),
entity_registry_enabled_default=False,
),
),
"legacy_status": (
PrusaLinkSensorEntityDescription[LegacyPrinterStatus](
key="printer.telemetry.material",
translation_key="material",
value_fn=lambda data: cast(str, data["telemetry"]["material"]),
),
),
"job": (
PrusaLinkSensorEntityDescription[JobInfo](
key="job.progress",
translation_key="progress",
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: cast(float, data["progress"]),
available_fn=lambda data: (
data.get("progress") is not None
and data.get("state") != PrinterState.IDLE.value
),
),
PrusaLinkSensorEntityDescription[JobInfo](
key="job.filename",
translation_key="filename",
value_fn=lambda data: cast(str, data["file"]["display_name"]),
available_fn=lambda data: (
data.get("file") is not None
and data.get("state") != PrinterState.IDLE.value
),
),
PrusaLinkSensorEntityDescription[JobInfo](
key="job.start",
translation_key="print_start",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=ignore_variance(
lambda data: (utcnow() - timedelta(seconds=data["time_printing"])),
timedelta(minutes=2),
),
available_fn=lambda data: (
data.get("time_printing") is not None
and data.get("state") != PrinterState.IDLE.value
),
),
PrusaLinkSensorEntityDescription[JobInfo](
key="job.finish",
translation_key="print_finish",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=ignore_variance(
lambda data: (utcnow() + timedelta(seconds=data["time_remaining"])),
timedelta(minutes=2),
),
available_fn=lambda data: (
data.get("time_remaining") is not None
and data.get("state") != PrinterState.IDLE.value
),
),
),
"info": (
PrusaLinkSensorEntityDescription[PrinterInfo](
key="info.nozzle_diameter",
translation_key="nozzle_diameter",
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
device_class=SensorDeviceClass.DISTANCE,
value_fn=lambda data: cast(str, data["nozzle_diameter"]),
entity_registry_enabled_default=False,
),
),
}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up PrusaLink sensor based on a config entry."""
coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
entry.entry_id
]
entities: list[PrusaLinkEntity] = []
for coordinator_type, sensors in SENSORS.items():
coordinator = coordinators[coordinator_type]
entities.extend(
PrusaLinkSensorEntity(coordinator, sensor_description)
for sensor_description in sensors
)
async_add_entities(entities)
class PrusaLinkSensorEntity(PrusaLinkEntity, SensorEntity):
"""Defines a PrusaLink sensor."""
entity_description: PrusaLinkSensorEntityDescription
def __init__(
self,
coordinator: PrusaLinkUpdateCoordinator,
description: PrusaLinkSensorEntityDescription,
) -> None:
"""Initialize a PrusaLink sensor entity."""
super().__init__(coordinator=coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}"
@property
def native_value(self) -> datetime | StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)
@property
def available(self) -> bool:
"""Return if sensor is available."""
return super().available and self.entity_description.available_fn(
self.coordinator.data
)