core/homeassistant/components/canary/sensor.py

173 lines
5.7 KiB
Python

"""Support for Canary sensors."""
from __future__ import annotations
from typing import Final
from canary.model import Device, Location, SensorType
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DATA_COORDINATOR, DOMAIN, MANUFACTURER
from .coordinator import CanaryDataUpdateCoordinator
type SensorTypeItem = tuple[
str, str | None, str | None, SensorDeviceClass | None, list[str]
]
SENSOR_VALUE_PRECISION: Final = 2
ATTR_AIR_QUALITY: Final = "air_quality"
# Define variables to store the device names, as referred to by the Canary API.
# Note: If Canary change the name of any of their devices (which they have done),
# then these variables will need updating, otherwise the sensors will stop working
# and disappear in Home Assistant.
CANARY_PRO: Final = "Canary Pro"
CANARY_FLEX: Final = "Canary Flex"
# Sensor types are defined like so:
# sensor type name, unit_of_measurement, icon, device class, products supported
SENSOR_TYPES: Final[list[SensorTypeItem]] = [
(
"temperature",
UnitOfTemperature.CELSIUS,
None,
SensorDeviceClass.TEMPERATURE,
[CANARY_PRO],
),
("humidity", PERCENTAGE, None, SensorDeviceClass.HUMIDITY, [CANARY_PRO]),
("air_quality", None, "mdi:weather-windy", None, [CANARY_PRO]),
(
"wifi",
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
None,
SensorDeviceClass.SIGNAL_STRENGTH,
[CANARY_FLEX],
),
("battery", PERCENTAGE, None, SensorDeviceClass.BATTERY, [CANARY_FLEX]),
]
STATE_AIR_QUALITY_NORMAL: Final = "normal"
STATE_AIR_QUALITY_ABNORMAL: Final = "abnormal"
STATE_AIR_QUALITY_VERY_ABNORMAL: Final = "very_abnormal"
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Canary sensors based on a config entry."""
coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
DATA_COORDINATOR
]
sensors: list[CanarySensor] = []
for location in coordinator.data["locations"].values():
for device in location.devices:
if device.is_online:
device_type = device.device_type
sensors.extend(
CanarySensor(coordinator, sensor_type, location, device)
for sensor_type in SENSOR_TYPES
if device_type.get("name") in sensor_type[4]
)
async_add_entities(sensors, True)
class CanarySensor(CoordinatorEntity[CanaryDataUpdateCoordinator], SensorEntity):
"""Representation of a Canary sensor."""
def __init__(
self,
coordinator: CanaryDataUpdateCoordinator,
sensor_type: SensorTypeItem,
location: Location,
device: Device,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._sensor_type = sensor_type
self._device_id = device.device_id
sensor_type_name = sensor_type[0].replace("_", " ").title()
self._attr_name = f"{location.name} {device.name} {sensor_type_name}"
canary_sensor_type = None
if self._sensor_type[0] == "air_quality":
canary_sensor_type = SensorType.AIR_QUALITY
elif self._sensor_type[0] == "temperature":
canary_sensor_type = SensorType.TEMPERATURE
elif self._sensor_type[0] == "humidity":
canary_sensor_type = SensorType.HUMIDITY
elif self._sensor_type[0] == "wifi":
canary_sensor_type = SensorType.WIFI
elif self._sensor_type[0] == "battery":
canary_sensor_type = SensorType.BATTERY
self._canary_type = canary_sensor_type
self._attr_unique_id = f"{device.device_id}_{sensor_type[0]}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(device.device_id))},
model=device.device_type["name"],
manufacturer=MANUFACTURER,
name=device.name,
)
self._attr_native_unit_of_measurement = sensor_type[1]
self._attr_device_class = sensor_type[3]
self._attr_icon = sensor_type[2]
@property
def reading(self) -> float | None:
"""Return the device sensor reading."""
readings = self.coordinator.data["readings"][self._device_id]
value = next(
(
reading.value
for reading in readings
if reading.sensor_type == self._canary_type
),
None,
)
if value is not None:
return round(float(value), SENSOR_VALUE_PRECISION)
return None
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.reading
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the state attributes."""
reading = self.reading
if self._sensor_type[0] == "air_quality" and reading is not None:
air_quality = None
if reading <= 0.4:
air_quality = STATE_AIR_QUALITY_VERY_ABNORMAL
elif reading <= 0.59:
air_quality = STATE_AIR_QUALITY_ABNORMAL
else:
air_quality = STATE_AIR_QUALITY_NORMAL
return {ATTR_AIR_QUALITY: air_quality}
return None