diff --git a/homeassistant/components/xiaomi_miio/binary_sensor.py b/homeassistant/components/xiaomi_miio/binary_sensor.py index d33059c20ef..1bdd647da79 100644 --- a/homeassistant/components/xiaomi_miio/binary_sensor.py +++ b/homeassistant/components/xiaomi_miio/binary_sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass +import logging from typing import Callable from homeassistant.components.binary_sensor import ( @@ -12,6 +13,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntityDescription, ) from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC +from homeassistant.core import callback from . import VacuumCoordinatorDataAttributes from .const import ( @@ -30,6 +32,8 @@ from .const import ( ) from .device import XiaomiCoordinatedMiioEntity +_LOGGER = logging.getLogger(__name__) + ATTR_NO_WATER = "no_water" ATTR_POWERSUPPLY_ATTACHED = "powersupply_attached" ATTR_WATER_TANK_DETACHED = "water_tank_detached" @@ -108,21 +112,29 @@ HUMIDIFIER_MJJSQ_BINARY_SENSORS = (ATTR_NO_WATER, ATTR_WATER_TANK_DETACHED) def _setup_vacuum_sensors(hass, config_entry, async_add_entities): """Only vacuums with mop should have binary sensor registered.""" - if config_entry.data[CONF_MODEL] not in MODELS_VACUUM_WITH_MOP: return device = hass.data[DOMAIN][config_entry.entry_id].get(KEY_DEVICE) + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] entities = [] for sensor, description in VACUUM_SENSORS.items(): + parent_key_data = getattr(coordinator.data, description.parent_key) + if getattr(parent_key_data, description.key, None) is None: + _LOGGER.debug( + "It seems the %s does not support the %s as the initial value is None", + config_entry.data[CONF_MODEL], + description.key, + ) + continue entities.append( XiaomiGenericBinarySensor( f"{config_entry.title} {description.name}", device, config_entry, f"{sensor}_{config_entry.unique_id}", - hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], + coordinator, description, ) ) @@ -168,18 +180,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class XiaomiGenericBinarySensor(XiaomiCoordinatedMiioEntity, BinarySensorEntity): """Representation of a Xiaomi Humidifier binary sensor.""" + entity_description: XiaomiMiioBinarySensorDescription + def __init__(self, name, device, entry, unique_id, coordinator, description): """Initialize the entity.""" super().__init__(name, device, entry, unique_id, coordinator) - self.entity_description: XiaomiMiioBinarySensorDescription = description + self.entity_description = description self._attr_entity_registry_enabled_default = ( description.entity_registry_enabled_default ) + self._attr_is_on = self._determine_native_value() - @property - def is_on(self): - """Return true if the binary sensor is on.""" + @callback + def _handle_coordinator_update(self) -> None: + self._attr_is_on = self._determine_native_value() + + super()._handle_coordinator_update() + + def _determine_native_value(self): + """Determine native value.""" if self.entity_description.parent_key is not None: return self._extract_value_from_attribute( getattr(self.coordinator.data, self.entity_description.parent_key), diff --git a/homeassistant/components/xiaomi_miio/device.py b/homeassistant/components/xiaomi_miio/device.py index 8203b021ef9..488f4cc066f 100644 --- a/homeassistant/components/xiaomi_miio/device.py +++ b/homeassistant/components/xiaomi_miio/device.py @@ -169,17 +169,8 @@ class XiaomiCoordinatedMiioEntity(CoordinatorEntity): return cls._parse_datetime_datetime(value) if isinstance(value, datetime.timedelta): return cls._parse_time_delta(value) - if isinstance(value, float): - return value - if isinstance(value, int): - return value - - _LOGGER.warning( - "Could not determine how to parse state value of type %s for state %s and attribute %s", - type(value), - type(state), - attribute, - ) + if value is None: + _LOGGER.debug("Attribute %s is None, this is unexpected", attribute) return value diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 01304008b76..07ec4613270 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -1,7 +1,6 @@ """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier.""" from abc import abstractmethod import asyncio -from enum import Enum import logging import math @@ -363,14 +362,6 @@ class XiaomiGenericAirPurifier(XiaomiGenericDevice): return None - @staticmethod - def _extract_value_from_attribute(state, attribute): - value = getattr(state, attribute) - if isinstance(value, Enum): - return value.value - - return value - @callback def _handle_coordinator_update(self): """Fetch state from the device.""" diff --git a/homeassistant/components/xiaomi_miio/humidifier.py b/homeassistant/components/xiaomi_miio/humidifier.py index 411d1428c70..9896bf8f0ea 100644 --- a/homeassistant/components/xiaomi_miio/humidifier.py +++ b/homeassistant/components/xiaomi_miio/humidifier.py @@ -1,5 +1,4 @@ """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier with humidifier entity.""" -from enum import Enum import logging import math @@ -124,14 +123,6 @@ class XiaomiGenericHumidifier(XiaomiCoordinatedMiioEntity, HumidifierEntity): """Return true if device is on.""" return self._state - @staticmethod - def _extract_value_from_attribute(state, attribute): - value = getattr(state, attribute) - if isinstance(value, Enum): - return value.value - - return value - @property def mode(self): """Get the current mode.""" diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 1461f33add6..161a690a0df 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -2,7 +2,6 @@ from __future__ import annotations from dataclasses import dataclass -from enum import Enum from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.const import DEGREE, ENTITY_CATEGORY_CONFIG, TIME_MINUTES @@ -285,14 +284,6 @@ class XiaomiNumberEntity(XiaomiCoordinatedMiioEntity, NumberEntity): return False return super().available - @staticmethod - def _extract_value_from_attribute(state, attribute): - value = getattr(state, attribute) - if isinstance(value, Enum): - return value.value - - return value - async def async_set_value(self, value): """Set an option of the miio device.""" method = getattr(self, self.entity_description.method) diff --git a/homeassistant/components/xiaomi_miio/select.py b/homeassistant/components/xiaomi_miio/select.py index 2753fb09786..ec1be6f3219 100644 --- a/homeassistant/components/xiaomi_miio/select.py +++ b/homeassistant/components/xiaomi_miio/select.py @@ -2,7 +2,6 @@ from __future__ import annotations from dataclasses import dataclass -from enum import Enum from miio.airfresh import LedBrightness as AirfreshLedBrightness from miio.airhumidifier import LedBrightness as AirhumidifierLedBrightness @@ -126,14 +125,6 @@ class XiaomiSelector(XiaomiCoordinatedMiioEntity, SelectEntity): self._attr_options = list(description.options) self.entity_description = description - @staticmethod - def _extract_value_from_attribute(state, attribute): - value = getattr(state, attribute) - if isinstance(value, Enum): - return value.value - - return value - class XiaomiAirHumidifierSelector(XiaomiSelector): """Representation of a Xiaomi Air Humidifier selector.""" @@ -153,7 +144,7 @@ class XiaomiAirHumidifierSelector(XiaomiSelector): ) # Sometimes (quite rarely) the device returns None as the LED brightness so we # check that the value is not None before updating the state. - if led_brightness: + if led_brightness is not None: self._current_led_brightness = led_brightness self.async_write_ha_state() diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index e1e2d91ad1a..f818a809a5c 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -48,6 +48,7 @@ from homeassistant.const import ( TIME_SECONDS, VOLUME_CUBIC_METERS, ) +from homeassistant.core import callback from . import VacuumCoordinatorDataAttributes from .const import ( @@ -529,17 +530,27 @@ VACUUM_SENSORS = { def _setup_vacuum_sensors(hass, config_entry, async_add_entities): + """Set up the Xiaomi vacuum sensors.""" device = hass.data[DOMAIN][config_entry.entry_id].get(KEY_DEVICE) + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] entities = [] for sensor, description in VACUUM_SENSORS.items(): + parent_key_data = getattr(coordinator.data, description.parent_key) + if getattr(parent_key_data, description.key, None) is None: + _LOGGER.debug( + "It seems the %s does not support the %s as the initial value is None", + config_entry.data[CONF_MODEL], + description.key, + ) + continue entities.append( XiaomiGenericSensor( f"{config_entry.title} {description.name}", device, config_entry, f"{sensor}_{config_entry.unique_id}", - hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], + coordinator, description, ) ) @@ -637,23 +648,41 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity): """Representation of a Xiaomi generic sensor.""" - def __init__( - self, - name, - device, - entry, - unique_id, - coordinator, - description: XiaomiMiioSensorDescription, - ): + entity_description: XiaomiMiioSensorDescription + + def __init__(self, name, device, entry, unique_id, coordinator, description): """Initialize the entity.""" super().__init__(name, device, entry, unique_id, coordinator) + self.entity_description = description self._attr_unique_id = unique_id - self.entity_description: XiaomiMiioSensorDescription = description + self._attr_native_value = self._determine_native_value() + self._attr_extra_state_attributes = self._extract_attributes(coordinator.data) - @property - def native_value(self): - """Return the state of the device.""" + @callback + def _extract_attributes(self, data): + """Return state attributes with valid values.""" + return { + attr: value + for attr in self.entity_description.attributes + if hasattr(data, attr) + and (value := self._extract_value_from_attribute(data, attr)) is not None + } + + @callback + def _handle_coordinator_update(self): + """Fetch state from the device.""" + native_value = self._determine_native_value() + # Sometimes (quite rarely) the device returns None as the sensor value so we + # check that the value is not None before updating the state. + if native_value is not None: + self._attr_native_value = native_value + self._attr_extra_state_attributes = self._extract_attributes( + self.coordinator.data + ) + self.async_write_ha_state() + + def _determine_native_value(self): + """Determine native value.""" if self.entity_description.parent_key is not None: return self._extract_value_from_attribute( getattr(self.coordinator.data, self.entity_description.parent_key), @@ -664,15 +693,6 @@ class XiaomiGenericSensor(XiaomiCoordinatedMiioEntity, SensorEntity): self.coordinator.data, self.entity_description.key ) - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return { - attr: self._extract_value_from_attribute(self.coordinator.data, attr) - for attr in self.entity_description.attributes - if hasattr(self.coordinator.data, attr) - } - class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity): """Representation of a Xiaomi Air Quality Monitor.""" diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 5c29253ae73..ab825e2485d 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from dataclasses import dataclass -from enum import Enum from functools import partial import logging @@ -474,14 +473,6 @@ class XiaomiGenericCoordinatedSwitch(XiaomiCoordinatedMiioEntity, SwitchEntity): return False return super().available - @staticmethod - def _extract_value_from_attribute(state, attribute): - value = getattr(state, attribute) - if isinstance(value, Enum): - return value.value - - return value - async def async_turn_on(self, **kwargs) -> None: """Turn on an option of the miio device.""" method = getattr(self, self.entity_description.method_on)