From 8e76948297b8e3cea4831bdd8ce8b809595f3646 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 14 Mar 2022 07:58:34 +0100 Subject: [PATCH] Add binary_sensor platform for Sensibo (#68088) --- .coveragerc | 1 + .../components/sensibo/binary_sensor.py | 179 ++++++++++++++++++ homeassistant/components/sensibo/const.py | 8 +- .../components/sensibo/coordinator.py | 8 + 4 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/sensibo/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index a10f3ca997f..778983e55fa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1011,6 +1011,7 @@ omit = homeassistant/components/senseme/light.py homeassistant/components/senseme/switch.py homeassistant/components/sensibo/__init__.py + homeassistant/components/sensibo/binary_sensor.py homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/coordinator.py homeassistant/components/sensibo/diagnostics.py diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py new file mode 100644 index 00000000000..fef81c6d7c1 --- /dev/null +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -0,0 +1,179 @@ +"""Binary Sensor platform for Sensibo integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, LOGGER +from .coordinator import MotionSensor, SensiboDataUpdateCoordinator +from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity + + +@dataclass +class MotionBaseEntityDescriptionMixin: + """Mixin for required Sensibo base description keys.""" + + value_fn: Callable[[MotionSensor], bool | None] + + +@dataclass +class DeviceBaseEntityDescriptionMixin: + """Mixin for required Sensibo base description keys.""" + + value_fn: Callable[[dict[str, Any]], bool | None] + + +@dataclass +class SensiboMotionBinarySensorEntityDescription( + BinarySensorEntityDescription, MotionBaseEntityDescriptionMixin +): + """Describes Sensibo Motion sensor entity.""" + + +@dataclass +class SensiboDeviceBinarySensorEntityDescription( + BinarySensorEntityDescription, DeviceBaseEntityDescriptionMixin +): + """Describes Sensibo Motion sensor entity.""" + + +MOTION_SENSOR_TYPES: tuple[SensiboMotionBinarySensorEntityDescription, ...] = ( + SensiboMotionBinarySensorEntityDescription( + key="alive", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + entity_category=EntityCategory.DIAGNOSTIC, + name="Alive", + icon="mdi:wifi", + value_fn=lambda data: data.alive, + ), + SensiboMotionBinarySensorEntityDescription( + key="is_main_sensor", + entity_category=EntityCategory.DIAGNOSTIC, + name="Main Sensor", + icon="mdi:connection", + value_fn=lambda data: data.is_main_sensor, + ), + SensiboMotionBinarySensorEntityDescription( + key="motion", + device_class=BinarySensorDeviceClass.MOTION, + name="Motion", + icon="mdi:motion-sensor", + value_fn=lambda data: data.motion, + ), +) + +DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( + SensiboDeviceBinarySensorEntityDescription( + key="room_occupied", + device_class=BinarySensorDeviceClass.MOTION, + name="Room Occupied", + icon="mdi:motion-sensor", + value_fn=lambda data: data["room_occupied"], + ), + SensiboDeviceBinarySensorEntityDescription( + key="update_available", + device_class=BinarySensorDeviceClass.UPDATE, + entity_category=EntityCategory.DIAGNOSTIC, + name="Update Available", + icon="mdi:rocket-launch", + value_fn=lambda data: data["update_available"], + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Sensibo binary sensor platform.""" + + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + entities: list[SensiboMotionSensor | SensiboDeviceSensor] = [] + LOGGER.debug("parsed data: %s", coordinator.data.parsed) + entities.extend( + SensiboMotionSensor(coordinator, device_id, sensor_id, sensor_data, description) + for device_id, device_data in coordinator.data.parsed.items() + for sensor_id, sensor_data in device_data["motion_sensors"].items() + for description in MOTION_SENSOR_TYPES + if device_data["motion_sensors"] + ) + LOGGER.debug("start device %s", entities) + entities.extend( + SensiboDeviceSensor(coordinator, device_id, description) + for description in DEVICE_SENSOR_TYPES + for device_id, device_data in coordinator.data.parsed.items() + if device_data[description.key] is not None + ) + LOGGER.debug("list: %s", entities) + + async_add_entities(entities) + + +class SensiboMotionSensor(SensiboMotionBaseEntity, BinarySensorEntity): + """Representation of a Sensibo Motion Binary Sensor.""" + + entity_description: SensiboMotionBinarySensorEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + sensor_id: str, + sensor_data: MotionSensor, + entity_description: SensiboMotionBinarySensorEntityDescription, + ) -> None: + """Initiate Sensibo Motion Binary Sensor.""" + super().__init__( + coordinator, + device_id, + sensor_id, + sensor_data, + entity_description.name, + ) + self.entity_description = entity_description + self._attr_unique_id = f"{sensor_id}-{entity_description.key}" + self._attr_name = ( + f"{self.device_data['name']} Motion Sensor {entity_description.name}" + ) + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self.entity_description.value_fn(self.sensor_data) + + +class SensiboDeviceSensor(SensiboDeviceBaseEntity, BinarySensorEntity): + """Representation of a Sensibo Device Binary Sensor.""" + + entity_description: SensiboDeviceBinarySensorEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + entity_description: SensiboDeviceBinarySensorEntityDescription, + ) -> None: + """Initiate Sensibo Device Binary Sensor.""" + super().__init__( + coordinator, + device_id, + ) + self.entity_description = entity_description + self._attr_unique_id = f"{device_id}-{entity_description.key}" + self._attr_name = f"{self.device_data['name']} {entity_description.name}" + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self.entity_description.value_fn(self.device_data) diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index d39f941d63e..59f0d1c1179 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -12,7 +12,13 @@ LOGGER = logging.getLogger(__package__) DEFAULT_SCAN_INTERVAL = 60 DOMAIN = "sensibo" -PLATFORMS = [Platform.CLIMATE, Platform.NUMBER, Platform.SELECT, Platform.SENSOR] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.CLIMATE, + Platform.NUMBER, + Platform.SELECT, + Platform.SENSOR, +] ALL = ["all"] DEFAULT_NAME = "Sensibo" TIMEOUT = 8 diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index b79ae9d0923..6aaf53a0e73 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -173,6 +173,12 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): pure_boost_enabled = pure_conf.get("enabled") if pure_conf else None pm25 = dev["measurements"].get("pm25") + # Binary sensors for main device + room_occupied = dev["roomIsOccupied"] + update_available = bool( + dev["firmwareVersion"] != dev["currentlyAvailableFirmwareVersion"] + ) + device_data[unique_id] = { "id": unique_id, "mac": mac, @@ -209,6 +215,8 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): "pure_sensitivity": pure_sensitivity, "pure_boost_enabled": pure_boost_enabled, "pm25": pm25, + "room_occupied": room_occupied, + "update_available": update_available, } return SensiboData(raw=data, parsed=device_data)