From 03a155af8358276d301591d1d19bddae8827e0c1 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 12 Mar 2022 21:45:24 +0100 Subject: [PATCH] Add sensors to Sensibo for motion sensor (#67748) --- .coveragerc | 1 + homeassistant/components/sensibo/climate.py | 4 +- homeassistant/components/sensibo/const.py | 2 +- .../components/sensibo/coordinator.py | 34 +++-- homeassistant/components/sensibo/entity.py | 71 ++++++++-- homeassistant/components/sensibo/number.py | 4 +- homeassistant/components/sensibo/select.py | 4 +- homeassistant/components/sensibo/sensor.py | 133 ++++++++++++++++++ 8 files changed, 217 insertions(+), 36 deletions(-) create mode 100644 homeassistant/components/sensibo/sensor.py diff --git a/.coveragerc b/.coveragerc index 26b24bfbe00..a10f3ca997f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1017,6 +1017,7 @@ omit = homeassistant/components/sensibo/entity.py homeassistant/components/sensibo/number.py homeassistant/components/sensibo/select.py + homeassistant/components/sensibo/sensor.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 669fa7a9543..9299952024c 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -37,7 +37,7 @@ from homeassistant.util.temperature import convert as convert_temperature from .const import ALL, DOMAIN, LOGGER from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboBaseEntity +from .entity import SensiboDeviceBaseEntity SERVICE_ASSUME_STATE = "assume_state" @@ -119,7 +119,7 @@ async def async_setup_entry( ) -class SensiboClimate(SensiboBaseEntity, ClimateEntity): +class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): """Representation of a Sensibo device.""" def __init__( diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index 9558376f079..d39f941d63e 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -12,7 +12,7 @@ LOGGER = logging.getLogger(__package__) DEFAULT_SCAN_INTERVAL = 60 DOMAIN = "sensibo" -PLATFORMS = [Platform.CLIMATE, Platform.NUMBER, Platform.SELECT] +PLATFORMS = [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 bceede8664e..7d37ae7f235 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -26,6 +26,7 @@ class MotionSensor: id: str alive: bool | None = None + motion: bool | None = None fw_ver: str | None = None fw_type: str | None = None is_main_sensor: bool | None = None @@ -33,6 +34,7 @@ class MotionSensor: humidity: int | None = None temperature: float | None = None model: str | None = None + rssi: int | None = None @dataclass @@ -147,21 +149,23 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): temperature = measurements.get("temperature") humidity = measurements.get("humidity") - motion_sensors = [ - MotionSensor( - id=motionsensor["id"], - alive=motionsensor["connectionStatus"].get("isAlive"), - fw_ver=motionsensor.get("firmwareVersion"), - fw_type=motionsensor.get("firmwareType"), - is_main_sensor=motionsensor.get("isMainSensor"), - battery_voltage=motionsensor["measurements"].get("batteryVoltage"), - humidity=motionsensor["measurements"].get("humidity"), - temperature=motionsensor["measurements"].get("temperature"), - model=motionsensor.get("productModel"), - ) - for motionsensor in dev["motionSensors"] - if dev["motionSensors"] - ] + motion_sensors: dict[str, Any] = {} + if dev["motionSensors"]: + for sensor in dev["motionSensors"]: + measurement = sensor["measurements"] + motion_sensors[sensor["id"]] = MotionSensor( + id=sensor["id"], + alive=sensor["connectionStatus"].get("isAlive"), + motion=measurement.get("motion"), + fw_ver=sensor.get("firmwareVersion"), + fw_type=sensor.get("firmwareType"), + is_main_sensor=sensor.get("isMainSensor"), + battery_voltage=measurement.get("batteryVoltage"), + humidity=measurement.get("humidity"), + temperature=measurement.get("temperature"), + model=sensor.get("productModel"), + rssi=measurement.get("rssi"), + ) device_data[unique_id] = { "id": unique_id, diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index 026cca4ddff..a2a2bbf3a0e 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -11,11 +11,11 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT -from .coordinator import SensiboDataUpdateCoordinator +from .coordinator import MotionSensor, SensiboDataUpdateCoordinator class SensiboBaseEntity(CoordinatorEntity): - """Representation of a Sensibo numbers.""" + """Representation of a Sensibo entity.""" coordinator: SensiboDataUpdateCoordinator @@ -28,24 +28,35 @@ class SensiboBaseEntity(CoordinatorEntity): super().__init__(coordinator) self._device_id = device_id self._client = coordinator.client - device = coordinator.data.parsed[device_id] - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, device["id"])}, - name=device["name"], - connections={(CONNECTION_NETWORK_MAC, device["mac"])}, - manufacturer="Sensibo", - configuration_url="https://home.sensibo.com/", - model=device["model"], - sw_version=device["fw_ver"], - hw_version=device["fw_type"], - suggested_area=device["name"], - ) @property def device_data(self) -> dict[str, Any]: """Return data for device.""" return self.coordinator.data.parsed[self._device_id] + +class SensiboDeviceBaseEntity(SensiboBaseEntity): + """Representation of a Sensibo device.""" + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + ) -> None: + """Initiate Sensibo Number.""" + super().__init__(coordinator, device_id) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.device_data["id"])}, + name=self.device_data["name"], + connections={(CONNECTION_NETWORK_MAC, self.device_data["mac"])}, + manufacturer="Sensibo", + configuration_url="https://home.sensibo.com/", + model=self.device_data["model"], + sw_version=self.device_data["fw_ver"], + hw_version=self.device_data["fw_type"], + suggested_area=self.device_data["name"], + ) + async def async_send_command( self, command: str, params: dict[str, Any] ) -> dict[str, Any]: @@ -80,3 +91,35 @@ class SensiboBaseEntity(CoordinatorEntity): params["assumed_state"], ) return result + + +class SensiboMotionBaseEntity(SensiboBaseEntity): + """Representation of a Sensibo motion entity.""" + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + sensor_id: str, + sensor_data: MotionSensor, + name: str | None, + ) -> None: + """Initiate Sensibo Number.""" + super().__init__(coordinator, device_id) + self._sensor_id = sensor_id + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, sensor_id)}, + name=f"{self.device_data['name']} Motion Sensor {name}", + via_device=(DOMAIN, device_id), + manufacturer="Sensibo", + configuration_url="https://home.sensibo.com/", + model=sensor_data.model, + sw_version=sensor_data.fw_ver, + hw_version=sensor_data.fw_type, + ) + + @property + def sensor_data(self) -> MotionSensor: + """Return data for device.""" + return self.device_data["motion_sensors"][self._sensor_id] diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index 2aa38a41a7b..d3cc4d57830 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboBaseEntity +from .entity import SensiboDeviceBaseEntity @dataclass @@ -70,7 +70,7 @@ async def async_setup_entry( ) -class SensiboNumber(SensiboBaseEntity, NumberEntity): +class SensiboNumber(SensiboDeviceBaseEntity, NumberEntity): """Representation of a Sensibo numbers.""" entity_description: SensiboNumberEntityDescription diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index 3f615f06afe..cb569620cca 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboBaseEntity +from .entity import SensiboDeviceBaseEntity @dataclass @@ -62,7 +62,7 @@ async def async_setup_entry( ) -class SensiboSelect(SensiboBaseEntity, SelectEntity): +class SensiboSelect(SensiboDeviceBaseEntity, SelectEntity): """Representation of a Sensibo Select.""" entity_description: SensiboSelectEntityDescription diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py new file mode 100644 index 00000000000..cb5833ee985 --- /dev/null +++ b/homeassistant/components/sensibo/sensor.py @@ -0,0 +1,133 @@ +"""Sensor platform for Sensibo integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ELECTRIC_POTENTIAL_VOLT, + PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + TEMP_CELSIUS, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from .const import DOMAIN +from .coordinator import MotionSensor, SensiboDataUpdateCoordinator +from .entity import SensiboMotionBaseEntity + + +@dataclass +class BaseEntityDescriptionMixin: + """Mixin for required Sensibo base description keys.""" + + value_fn: Callable[[MotionSensor], StateType] + + +@dataclass +class SensiboSensorEntityDescription( + SensorEntityDescription, BaseEntityDescriptionMixin +): + """Describes Sensibo Motion sensor entity.""" + + +MOTION_SENSOR_TYPES: tuple[SensiboSensorEntityDescription, ...] = ( + SensiboSensorEntityDescription( + key="rssi", + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + state_class=SensorStateClass.MEASUREMENT, + name="rssi", + icon="mdi:wifi", + value_fn=lambda data: data.rssi, + entity_registry_enabled_default=False, + ), + SensiboSensorEntityDescription( + key="battery_voltage", + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + state_class=SensorStateClass.MEASUREMENT, + name="Battery Voltage", + icon="mdi:battery", + value_fn=lambda data: data.battery_voltage, + ), + SensiboSensorEntityDescription( + key="humidity", + device_class=SensorDeviceClass.HUMIDITY, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + name="Humidity", + icon="mdi:water", + value_fn=lambda data: data.humidity, + ), + SensiboSensorEntityDescription( + key="temperature", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + name="Temperature", + icon="mdi:thermometer", + value_fn=lambda data: data.temperature, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Sensibo sensor platform.""" + + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + 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"] + ) + + +class SensiboMotionSensor(SensiboMotionBaseEntity, SensorEntity): + """Representation of a Sensibo Motion Sensor.""" + + entity_description: SensiboSensorEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + sensor_id: str, + sensor_data: MotionSensor, + entity_description: SensiboSensorEntityDescription, + ) -> None: + """Initiate Sensibo Motion 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 native_value(self) -> StateType: + """Return value of sensor.""" + return self.entity_description.value_fn(self.sensor_data)