SWS-12500-custom-component/custom_components/sws12500/battery_sensors.py

69 lines
2.5 KiB
Python

"""Binary sensor entity for SWS 12500.
One class serves every binary family (battery, water leak). Which raw value means
`on` comes from the description's `on_value`, because the station's convention
differs per family - see `WSBinarySensorEntityDescription`.
"""
from __future__ import annotations
from functools import cached_property
from typing import Any
from py_typecheck import checked_or
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .data import build_device_info
from .sensors_common import WSBinarySensorEntityDescription
from .utils import to_int
class WSBinarySensor( # pyright: ignore[reportIncompatibleVariableOverride]
CoordinatorEntity, BinarySensorEntity
):
"""Represent a WSLink binary reading.
Battery: the station sends ``0`` for low, and ``BinarySensorDeviceClass.BATTERY``
means ``on`` = low, so ``on_value`` is 0.
Water leak: the station sends ``1`` for a leak, and
``BinarySensorDeviceClass.MOISTURE`` means ``on`` = wet, so ``on_value`` is 1.
"""
entity_description: WSBinarySensorEntityDescription # pyright: ignore[reportIncompatibleVariableOverride]
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(
self,
coordinator: Any,
description: WSBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
super().__init__(coordinator)
self.entity_description = description # pyright: ignore[reportIncompatibleVariableOverride]
self._attr_unique_id = f"{description.key}_binary"
@property
def is_on(self) -> bool | None: # pyright: ignore[reportIncompatibleVariableOverride]
"""Return whether the reading matches this sensor's `on` value."""
data = checked_or(self.coordinator.data, dict[str, Any], {})
raw: Any = data.get(self.entity_description.key)
# `to_int` rather than a bare `int`: the station sometimes sends integer
# fields as decimals, and a leak reported as "1.0" must still read as wet.
value = to_int(raw)
if value is None:
return None
return value == self.entity_description.on_value
@cached_property
def device_info(self) -> DeviceInfo:
"""Device info (single shared device for the whole integration)."""
return build_device_info(self.coordinator.config)