From 84ad318e355ee4ba7b73801c2d76ada57317dcb2 Mon Sep 17 00:00:00 2001 From: Vincent Le Bourlot Date: Sat, 30 Jul 2022 09:59:00 +0200 Subject: [PATCH] Add missing entities to core 300S (#37) * Add filter life sensor * Add switch for child_lock * Update switch.py * Update switch.py * Update common.py * bump trunk version * improve feature discovery * fix style * improve feature discovery --- custom_components/vesync/binary_sensor.py | 21 +++---- custom_components/vesync/common.py | 24 ++++---- custom_components/vesync/light.py | 11 +--- custom_components/vesync/number.py | 55 +++++++----------- custom_components/vesync/sensor.py | 46 ++++++++++++--- custom_components/vesync/switch.py | 71 ++++++++++++++--------- 6 files changed, 126 insertions(+), 102 deletions(-) diff --git a/custom_components/vesync/binary_sensor.py b/custom_components/vesync/binary_sensor.py index 41dc5b2..25da20d 100644 --- a/custom_components/vesync/binary_sensor.py +++ b/custom_components/vesync/binary_sensor.py @@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import VeSyncBaseEntity, is_humidifier +from .common import VeSyncBaseEntity, has_feature from .const import DOMAIN, VS_BINARY_SENSORS, VS_DISCOVERY _LOGGER = logging.getLogger(__name__) @@ -40,20 +40,15 @@ def _setup_entities(devices, async_add_entities): """Check if device is online and add entity.""" entities = [] for dev in devices: - if is_humidifier(dev.device_type): - entities.extend( - (VeSyncOutOfWaterSensor(dev), VeSyncWaterTankLiftedSensor(dev)) - ) - - else: - _LOGGER.warning( - "%s - Unknown device type - %s", dev.device_name, dev.device_type - ) + if has_feature(dev, "details", "water_lacks"): + entities.append(VeSyncOutOfWaterSensor(dev)) + if has_feature(dev, "details", "water_tank_lifted"): + entities.append(VeSyncWaterTankLiftedSensor(dev)) async_add_entities(entities, update_before_add=True) -class VeSyncHumidifierBinarySensorEntity(VeSyncBaseEntity, BinarySensorEntity): +class VeSyncBinarySensorEntity(VeSyncBaseEntity, BinarySensorEntity): """Representation of a binary sensor describing diagnostics of a VeSync humidifier.""" def __init__(self, humidifier): @@ -67,7 +62,7 @@ class VeSyncHumidifierBinarySensorEntity(VeSyncBaseEntity, BinarySensorEntity): return EntityCategory.DIAGNOSTIC -class VeSyncOutOfWaterSensor(VeSyncHumidifierBinarySensorEntity): +class VeSyncOutOfWaterSensor(VeSyncBinarySensorEntity): """Out of Water Sensor.""" @property @@ -86,7 +81,7 @@ class VeSyncOutOfWaterSensor(VeSyncHumidifierBinarySensorEntity): return self.smarthumidifier.details["water_lacks"] -class VeSyncWaterTankLiftedSensor(VeSyncHumidifierBinarySensorEntity): +class VeSyncWaterTankLiftedSensor(VeSyncBinarySensorEntity): """Tank Lifted Sensor.""" @property diff --git a/custom_components/vesync/common.py b/custom_components/vesync/common.py index c770b39..c6a838b 100644 --- a/custom_components/vesync/common.py +++ b/custom_components/vesync/common.py @@ -18,6 +18,11 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +def has_feature(device, dictionary, attribute): + """Return the detail of the attribute.""" + return getattr(device, dictionary, None).get(attribute, None) is not None + + def is_humidifier(device_type: str) -> bool: """Return true if the device type is a humidifier.""" return model_features(device_type)["module"].find("VeSyncHumid") > -1 @@ -48,21 +53,14 @@ async def async_process_devices(hass, manager): _LOGGER.debug("Found a fan: %s", fan.__dict__) if is_humidifier(fan.device_type): devices[VS_HUMIDIFIERS].append(fan) - devices[VS_NUMBERS].append(fan) # for night light and mist level - devices[VS_SWITCHES].append(fan) # for automatic stop and display - devices[VS_SENSORS].append(fan) # for humidity sensor - devices[VS_BINARY_SENSORS].append( - fan - ) # for out of water and water tank lifted sensors - if fan.night_light: - devices[VS_LIGHTS].append(fan) # for night light else: - if hasattr(fan, "config_dict"): - devices[VS_NUMBERS].append(fan) - if "air_quality" in fan.config_dict["features"]: - devices[VS_SENSORS].append(fan) - devices[VS_SWITCHES].append(fan) # for automatic stop and display devices[VS_FANS].append(fan) + devices[VS_NUMBERS].append(fan) + devices[VS_SWITCHES].append(fan) + devices[VS_SENSORS].append(fan) + devices[VS_BINARY_SENSORS].append(fan) + devices[VS_LIGHTS].append(fan) + _LOGGER.info("%d VeSync fans found", len(manager.fans)) if manager.bulbs: diff --git a/custom_components/vesync/light.py b/custom_components/vesync/light.py index afb8ab7..87cf27b 100644 --- a/custom_components/vesync/light.py +++ b/custom_components/vesync/light.py @@ -14,7 +14,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import VeSyncDevice, is_humidifier +from .common import VeSyncDevice from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS _LOGGER = logging.getLogger(__name__) @@ -48,15 +48,10 @@ def _setup_entities(devices, async_add_entities): for dev in devices: if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"): entities.append(VeSyncDimmableLightHA(dev)) - elif DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",): + if DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",): entities.append(VeSyncTunableWhiteLightHA(dev)) - elif is_humidifier(dev.device_type): + if dev.night_light: entities.append(VeSyncHumidifierNightLightHA(dev)) - else: - _LOGGER.debug( - "%s - Unknown device type - %s", dev.device_name, dev.device_type - ) - continue async_add_entities(entities, update_before_add=True) diff --git a/custom_components/vesync/number.py b/custom_components/vesync/number.py index 36ec3d1..d3e4260 100644 --- a/custom_components/vesync/number.py +++ b/custom_components/vesync/number.py @@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import VeSyncBaseEntity, is_air_purifier, is_humidifier +from .common import VeSyncBaseEntity, has_feature, is_air_purifier, is_humidifier from .const import DOMAIN, VS_DISCOVERY, VS_NUMBERS MAX_HUMIDITY = 80 @@ -43,29 +43,28 @@ def _setup_entities(devices, async_add_entities): """Check if device is online and add entity.""" entities = [] for dev in devices: - if is_humidifier(dev.device_type): - ext = (VeSyncHumidifierMistLevelHA(dev), VeSyncHumidifierTargetLevelHA(dev)) - if dev.warm_mist_feature: - ext = (*ext, VeSyncHumidifierWarmthLevelHA(dev)) - entities.extend(ext) - elif is_air_purifier(dev.device_type): - entities.extend((VeSyncFanSpeedLevelHA(dev),)) - else: - _LOGGER.debug( - "%s - Unknown device type - %s", dev.device_name, dev.device_type - ) - continue + if has_feature(dev, "details", "mist_virtual_level"): + entities.append(VeSyncHumidifierMistLevelHA(dev)) + if has_feature(dev, "config", "auto_target_humidity"): + entities.append(VeSyncHumidifierTargetLevelHA(dev)) + if has_feature(dev, "details", "warm_mist_level"): + entities.append(VeSyncHumidifierWarmthLevelHA(dev)) + if has_feature(dev, "config_dict", "levels"): + entities.append((VeSyncFanSpeedLevelHA(dev),)) async_add_entities(entities, update_before_add=True) -class VeSyncFanNumberEntity(VeSyncBaseEntity, NumberEntity): +class VeSyncNumberEntity(VeSyncBaseEntity, NumberEntity): """Representation of a number for configuring a VeSync fan.""" - def __init__(self, fan): + def __init__(self, device): """Initialize the VeSync fan device.""" - super().__init__(fan) - self.smartfan = fan + super().__init__(device) + if is_air_purifier(device.device_type): + self.smartfan = device + if is_humidifier(device.device_type): + self.smarthumidifier = device @property def entity_category(self): @@ -73,7 +72,7 @@ class VeSyncFanNumberEntity(VeSyncBaseEntity, NumberEntity): return EntityCategory.CONFIG -class VeSyncFanSpeedLevelHA(VeSyncFanNumberEntity): +class VeSyncFanSpeedLevelHA(VeSyncNumberEntity): """Representation of the fan speed level of a VeSync fan.""" @property @@ -116,21 +115,7 @@ class VeSyncFanSpeedLevelHA(VeSyncFanNumberEntity): self.device.change_fan_speed(int(value)) -class VeSyncHumidifierNumberEntity(VeSyncBaseEntity, NumberEntity): - """Representation of a number for configuring a VeSync humidifier.""" - - def __init__(self, humidifier): - """Initialize the VeSync humidifier device.""" - super().__init__(humidifier) - self.smarthumidifier = humidifier - - @property - def entity_category(self): - """Return the diagnostic entity category.""" - return EntityCategory.CONFIG - - -class VeSyncHumidifierMistLevelHA(VeSyncHumidifierNumberEntity): +class VeSyncHumidifierMistLevelHA(VeSyncNumberEntity): """Representation of the mist level of a VeSync humidifier.""" @property @@ -173,7 +158,7 @@ class VeSyncHumidifierMistLevelHA(VeSyncHumidifierNumberEntity): self.device.set_mist_level(int(value)) -class VeSyncHumidifierWarmthLevelHA(VeSyncHumidifierNumberEntity): +class VeSyncHumidifierWarmthLevelHA(VeSyncNumberEntity): """Representation of the warmth level of a VeSync humidifier.""" @property @@ -216,7 +201,7 @@ class VeSyncHumidifierWarmthLevelHA(VeSyncHumidifierNumberEntity): self.device.set_warm_level(int(value)) -class VeSyncHumidifierTargetLevelHA(VeSyncHumidifierNumberEntity): +class VeSyncHumidifierTargetLevelHA(VeSyncNumberEntity): """Representation of the target humidity level of a VeSync humidifier.""" @property diff --git a/custom_components/vesync/sensor.py b/custom_components/vesync/sensor.py index c9db411..00e27af 100644 --- a/custom_components/vesync/sensor.py +++ b/custom_components/vesync/sensor.py @@ -13,7 +13,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import VeSyncBaseEntity, is_humidifier +from .common import VeSyncBaseEntity, has_feature from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_SENSORS _LOGGER = logging.getLogger(__name__) @@ -47,14 +47,12 @@ def _setup_entities(devices, async_add_entities): for dev in devices: if DEV_TYPE_TO_HA.get(dev.device_type) == "outlet": entities.extend((VeSyncPowerSensor(dev), VeSyncEnergySensor(dev))) - elif is_humidifier(dev.device_type): + if has_feature(dev, "details", "humidity"): entities.append(VeSyncHumiditySensor(dev)) - elif "air_quality" in dev.config_dict["features"]: + if has_feature(dev, "details", "air_quality"): entities.append(VeSyncAirQualitySensor(dev)) - else: - _LOGGER.warning( - "%s - Unknown device type - %s", dev.device_name, dev.device_type - ) + if has_feature(dev, "details", "filter_life"): + entities.append(VeSyncFilterLifeSensor(dev)) async_add_entities(entities, update_before_add=True) @@ -204,6 +202,40 @@ class VeSyncAirQualitySensor(VeSyncHumidifierSensorEntity): return SensorStateClass.MEASUREMENT +class VeSyncFilterLifeSensor(VeSyncHumidifierSensorEntity): + """Representation of a filter life sensor.""" + + @property + def unique_id(self): + """Return unique ID for filter life sensor on device.""" + return f"{super().unique_id}-filter-life" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} filter life" + + @property + def device_class(self): + """Return the filter life device class.""" + return None + + @property + def native_value(self): + """Return the filter life index.""" + return self.smarthumidifier.details["filter_life"] + + @property + def native_unit_of_measurement(self): + """Return the % unit of measurement.""" + return PERCENTAGE + + @property + def state_class(self): + """Return the measurement state class.""" + return SensorStateClass.MEASUREMENT + + class VeSyncHumiditySensor(VeSyncHumidifierSensorEntity): """Representation of current humidity for a VeSync humidifier.""" diff --git a/custom_components/vesync/switch.py b/custom_components/vesync/switch.py index ceea339..52f8402 100644 --- a/custom_components/vesync/switch.py +++ b/custom_components/vesync/switch.py @@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import VeSyncBaseEntity, VeSyncDevice, is_humidifier +from .common import VeSyncBaseEntity, VeSyncDevice from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_SWITCHES _LOGGER = logging.getLogger(__name__) @@ -42,24 +42,16 @@ def _setup_entities(devices, async_add_entities): for dev in devices: if DEV_TYPE_TO_HA.get(dev.device_type) == "outlet": entities.append(VeSyncSwitchHA(dev)) - elif DEV_TYPE_TO_HA.get(dev.device_type) == "switch": + if DEV_TYPE_TO_HA.get(dev.device_type) == "switch": entities.append(VeSyncLightSwitch(dev)) - elif is_humidifier(dev.device_type): - entities.extend( - ( - VeSyncHumidifierDisplayHA(dev), - VeSyncHumidifierAutomaticStopHA(dev), - VeSyncHumidifierAutoOnHA(dev), - ) - ) - elif getattr(dev, "turn_on_display", None): + if getattr(dev, "set_auto_mode", None): + entities.append(VeSyncHumidifierAutoOnHA(dev)) + if getattr(dev, "automatic_stop_on", None): + entities.append(VeSyncHumidifierAutomaticStopHA(dev)) + if getattr(dev, "turn_on_display", None): entities.append(VeSyncHumidifierDisplayHA(dev)) - - else: - _LOGGER.warning( - "%s - Unknown device type - %s", dev.device_name, dev.device_type - ) - continue + if getattr(dev, "child_lock_on", None): + entities.append(VeSyncFanChildLockHA(dev)) async_add_entities(entities, update_before_add=True) @@ -109,7 +101,7 @@ class VeSyncLightSwitch(VeSyncBaseSwitch, SwitchEntity): self.switch = switch -class VeSyncHumidifierSwitchEntity(VeSyncBaseEntity, SwitchEntity): +class VeSyncSwitchEntity(VeSyncBaseEntity, SwitchEntity): """Representation of a switch for configuring a VeSync humidifier.""" def __init__(self, humidifier): @@ -123,8 +115,35 @@ class VeSyncHumidifierSwitchEntity(VeSyncBaseEntity, SwitchEntity): return EntityCategory.CONFIG -class VeSyncHumidifierDisplayHA(VeSyncHumidifierSwitchEntity): - """Representation of the display on a VeSync humidifier.""" +class VeSyncFanChildLockHA(VeSyncSwitchEntity): + """Representation of the child lock switch.""" + + @property + def unique_id(self): + """Return the ID of this display.""" + return f"{super().unique_id}-child-lock" + + @property + def name(self): + """Return the name of the entity.""" + return f"{super().name} child lock" + + @property + def is_on(self): + """Return True if it is locked.""" + return self.device.details["child_locked"] + + def turn_on(self, **kwargs): + """Turn the lock on.""" + self.device.child_lock_on() + + def turn_off(self, **kwargs): + """Turn the lock off.""" + self.device.child_lock_off() + + +class VeSyncHumidifierDisplayHA(VeSyncSwitchEntity): + """Representation of the child lock switch.""" @property def unique_id(self): @@ -133,24 +152,24 @@ class VeSyncHumidifierDisplayHA(VeSyncHumidifierSwitchEntity): @property def name(self): - """Return the name of the display.""" + """Return the name of the entity.""" return f"{super().name} display" @property def is_on(self): - """Return True if display is on.""" + """Return True if it is locked.""" return self.device.details["display"] def turn_on(self, **kwargs): - """Turn the display on.""" + """Turn the lock on.""" self.device.turn_on_display() def turn_off(self, **kwargs): - """Turn the display off.""" + """Turn the lock off.""" self.device.turn_off_display() -class VeSyncHumidifierAutomaticStopHA(VeSyncHumidifierSwitchEntity): +class VeSyncHumidifierAutomaticStopHA(VeSyncSwitchEntity): """Representation of the automatic stop toggle on a VeSync humidifier.""" @property @@ -177,7 +196,7 @@ class VeSyncHumidifierAutomaticStopHA(VeSyncHumidifierSwitchEntity): self.device.automatic_stop_off() -class VeSyncHumidifierAutoOnHA(VeSyncHumidifierSwitchEntity): +class VeSyncHumidifierAutoOnHA(VeSyncSwitchEntity): """Provide switch to turn off auto mode and set manual mist level 1 on a VeSync humidifier.""" @property