2019-02-14 04:35:12 +00:00
|
|
|
"""Support for monitoring OctoPrint sensors."""
|
2021-10-22 13:25:12 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from datetime import timedelta
|
2016-05-04 01:35:11 +00:00
|
|
|
import logging
|
2016-09-02 10:26:23 +00:00
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
from pyoctoprintapi import OctoprintJobInfo, OctoprintPrinterInfo
|
2016-05-04 01:35:11 +00:00
|
|
|
|
2021-10-15 16:45:04 +00:00
|
|
|
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
|
2021-10-22 13:25:12 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.const import (
|
|
|
|
DEVICE_CLASS_TEMPERATURE,
|
|
|
|
DEVICE_CLASS_TIMESTAMP,
|
|
|
|
PERCENTAGE,
|
|
|
|
TEMP_CELSIUS,
|
|
|
|
)
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from homeassistant.helpers.update_coordinator import (
|
|
|
|
CoordinatorEntity,
|
|
|
|
DataUpdateCoordinator,
|
|
|
|
)
|
2016-09-02 10:26:23 +00:00
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
from . import DOMAIN as COMPONENT_DOMAIN
|
2019-03-21 05:56:46 +00:00
|
|
|
|
2016-09-02 10:26:23 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2016-05-04 01:35:11 +00:00
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
config_entry: ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Set up the available OctoPrint binary sensors."""
|
|
|
|
coordinator: DataUpdateCoordinator = hass.data[COMPONENT_DOMAIN][
|
|
|
|
config_entry.entry_id
|
|
|
|
]["coordinator"]
|
|
|
|
device_id = config_entry.unique_id
|
|
|
|
|
|
|
|
assert device_id is not None
|
|
|
|
|
|
|
|
entities: list[SensorEntity] = []
|
|
|
|
if coordinator.data["printer"]:
|
|
|
|
printer_info = coordinator.data["printer"]
|
|
|
|
types = ["actual", "target"]
|
|
|
|
for tool in printer_info.temperatures:
|
|
|
|
for temp_type in types:
|
|
|
|
entities.append(
|
|
|
|
OctoPrintTemperatureSensor(
|
|
|
|
coordinator,
|
|
|
|
tool.name,
|
|
|
|
temp_type,
|
|
|
|
device_id,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2021-10-22 13:25:12 +00:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
_LOGGER.error("Printer appears to be offline, skipping temperature sensors")
|
|
|
|
|
|
|
|
entities.append(OctoPrintStatusSensor(coordinator, device_id))
|
|
|
|
entities.append(OctoPrintJobPercentageSensor(coordinator, device_id))
|
|
|
|
entities.append(OctoPrintEstimatedFinishTimeSensor(coordinator, device_id))
|
|
|
|
entities.append(OctoPrintStartTimeSensor(coordinator, device_id))
|
|
|
|
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
|
|
|
|
|
|
class OctoPrintSensorBase(CoordinatorEntity, SensorEntity):
|
2016-05-07 01:03:18 +00:00
|
|
|
"""Representation of an OctoPrint sensor."""
|
2016-05-04 01:35:11 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2021-10-22 13:25:12 +00:00
|
|
|
coordinator: DataUpdateCoordinator,
|
|
|
|
sensor_type: str,
|
|
|
|
device_id: str,
|
|
|
|
) -> None:
|
2016-05-04 01:35:11 +00:00
|
|
|
"""Initialize a new OctoPrint sensor."""
|
2021-10-22 13:25:12 +00:00
|
|
|
super().__init__(coordinator)
|
|
|
|
self._device_id = device_id
|
2021-10-22 19:41:06 +00:00
|
|
|
self._attr_name = f"Octoprint {sensor_type}"
|
|
|
|
self._attr_unique_id = f"{sensor_type}-{device_id}"
|
2021-10-22 13:25:12 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self):
|
|
|
|
"""Device info."""
|
|
|
|
return {
|
|
|
|
"identifiers": {(COMPONENT_DOMAIN, self._device_id)},
|
|
|
|
"manufacturer": "Octoprint",
|
|
|
|
"name": "Octoprint",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class OctoPrintStatusSensor(OctoPrintSensorBase):
|
|
|
|
"""Representation of an OctoPrint sensor."""
|
|
|
|
|
2021-10-22 19:41:06 +00:00
|
|
|
_attr_icon = "mdi:printer-3d"
|
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
def __init__(self, coordinator: DataUpdateCoordinator, device_id: str) -> None:
|
|
|
|
"""Initialize a new OctoPrint sensor."""
|
|
|
|
super().__init__(coordinator, "Current State", device_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def native_value(self):
|
|
|
|
"""Return sensor state."""
|
|
|
|
printer: OctoprintPrinterInfo = self.coordinator.data["printer"]
|
|
|
|
if not printer:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return printer.state.text
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self) -> bool:
|
|
|
|
"""Return if entity is available."""
|
|
|
|
return self.coordinator.last_update_success and self.coordinator.data["printer"]
|
|
|
|
|
|
|
|
|
|
|
|
class OctoPrintJobPercentageSensor(OctoPrintSensorBase):
|
|
|
|
"""Representation of an OctoPrint sensor."""
|
|
|
|
|
2021-10-22 19:41:06 +00:00
|
|
|
_attr_native_unit_of_measurement = PERCENTAGE
|
|
|
|
_attr_icon = "mdi:file-percent"
|
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
def __init__(self, coordinator: DataUpdateCoordinator, device_id: str) -> None:
|
|
|
|
"""Initialize a new OctoPrint sensor."""
|
|
|
|
super().__init__(coordinator, "Job Percentage", device_id)
|
|
|
|
|
2016-05-04 01:35:11 +00:00
|
|
|
@property
|
2021-08-12 11:26:17 +00:00
|
|
|
def native_value(self):
|
2021-10-22 13:25:12 +00:00
|
|
|
"""Return sensor state."""
|
|
|
|
job: OctoprintJobInfo = self.coordinator.data["job"]
|
|
|
|
if not job:
|
|
|
|
return None
|
|
|
|
|
|
|
|
state = job.progress.completion
|
|
|
|
if not state:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
return round(state, 2)
|
2016-05-04 01:35:11 +00:00
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
|
|
|
|
class OctoPrintEstimatedFinishTimeSensor(OctoPrintSensorBase):
|
|
|
|
"""Representation of an OctoPrint sensor."""
|
|
|
|
|
2021-10-22 19:41:06 +00:00
|
|
|
_attr_device_class = DEVICE_CLASS_TIMESTAMP
|
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
def __init__(self, coordinator: DataUpdateCoordinator, device_id: str) -> None:
|
|
|
|
"""Initialize a new OctoPrint sensor."""
|
|
|
|
super().__init__(coordinator, "Estimated Finish Time", device_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def native_value(self):
|
|
|
|
"""Return sensor state."""
|
|
|
|
job: OctoprintJobInfo = self.coordinator.data["job"]
|
|
|
|
if not job or not job.progress.print_time_left or job.state != "Printing":
|
|
|
|
return None
|
|
|
|
|
|
|
|
read_time = self.coordinator.data["last_read_time"]
|
|
|
|
|
|
|
|
return (read_time + timedelta(seconds=job.progress.print_time_left)).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
class OctoPrintStartTimeSensor(OctoPrintSensorBase):
|
|
|
|
"""Representation of an OctoPrint sensor."""
|
|
|
|
|
2021-10-22 19:41:06 +00:00
|
|
|
_attr_device_class = DEVICE_CLASS_TIMESTAMP
|
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
def __init__(self, coordinator: DataUpdateCoordinator, device_id: str) -> None:
|
|
|
|
"""Initialize a new OctoPrint sensor."""
|
|
|
|
super().__init__(coordinator, "Start Time", device_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def native_value(self):
|
|
|
|
"""Return sensor state."""
|
|
|
|
job: OctoprintJobInfo = self.coordinator.data["job"]
|
|
|
|
|
|
|
|
if not job or not job.progress.print_time or job.state != "Printing":
|
|
|
|
return None
|
|
|
|
|
|
|
|
read_time = self.coordinator.data["last_read_time"]
|
|
|
|
|
|
|
|
return (read_time - timedelta(seconds=job.progress.print_time)).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
class OctoPrintTemperatureSensor(OctoPrintSensorBase):
|
|
|
|
"""Representation of an OctoPrint sensor."""
|
|
|
|
|
2021-10-22 19:41:06 +00:00
|
|
|
_attr_native_unit_of_measurement = TEMP_CELSIUS
|
|
|
|
_attr_device_class = DEVICE_CLASS_TEMPERATURE
|
|
|
|
_attr_state_class = STATE_CLASS_MEASUREMENT
|
|
|
|
|
2021-10-22 13:25:12 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
coordinator: DataUpdateCoordinator,
|
|
|
|
tool: str,
|
|
|
|
temp_type: str,
|
|
|
|
device_id: str,
|
|
|
|
) -> None:
|
|
|
|
"""Initialize a new OctoPrint sensor."""
|
|
|
|
super().__init__(coordinator, f"{temp_type} {tool} temp", device_id)
|
|
|
|
self._temp_type = temp_type
|
|
|
|
self._api_tool = tool
|
|
|
|
|
|
|
|
@property
|
|
|
|
def native_value(self):
|
|
|
|
"""Return sensor state."""
|
|
|
|
printer: OctoprintPrinterInfo = self.coordinator.data["printer"]
|
|
|
|
if not printer:
|
|
|
|
return None
|
|
|
|
|
|
|
|
for temp in printer.temperatures:
|
|
|
|
if temp.name == self._api_tool:
|
|
|
|
return round(
|
|
|
|
temp.actual_temp
|
|
|
|
if self._temp_type == "actual"
|
|
|
|
else temp.target_temp,
|
|
|
|
2,
|
|
|
|
)
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self) -> bool:
|
|
|
|
"""Return if entity is available."""
|
|
|
|
return self.coordinator.last_update_success and self.coordinator.data["printer"]
|