From a71d58f4b2835d73b4e1a33b38931a0816362697 Mon Sep 17 00:00:00 2001 From: HuffYk Date: Mon, 3 Jun 2024 14:55:56 +0200 Subject: [PATCH 1/5] added support for everestair device --- custom_components/vesync/binary_sensor.py | 20 +++ custom_components/vesync/common.py | 2 +- custom_components/vesync/const.py | 3 +- custom_components/vesync/fan.py | 13 +- custom_components/vesync/light.py | 4 +- custom_components/vesync/number.py | 8 +- custom_components/vesync/sensor.py | 200 ++++++++++++++++++++-- 7 files changed, 227 insertions(+), 23 deletions(-) diff --git a/custom_components/vesync/binary_sensor.py b/custom_components/vesync/binary_sensor.py index 38e3eb4..4236bf1 100644 --- a/custom_components/vesync/binary_sensor.py +++ b/custom_components/vesync/binary_sensor.py @@ -58,6 +58,8 @@ def _setup_entities(devices, async_add_entities, coordinator): entities.append(VeSyncOutOfWaterSensor(dev, coordinator)) if has_feature(dev, "details", "water_tank_lifted"): entities.append(VeSyncWaterTankLiftedSensor(dev, coordinator)) + if has_feature(dev, "details", "filter_open_state"): + entities.append(VeSyncFilterOpenStateSensor(dev, coordinator)) async_add_entities(entities, update_before_add=True) @@ -148,3 +150,21 @@ def name(self): def is_on(self) -> bool: """Return a value indicating whether the Humidifier's water tank is lifted.""" return self.smarthumidifier.details["water_tank_lifted"] + +class VeSyncFilterOpenStateSensor(VeSyncBinarySensorEntity): + """Filter Open Sensor.""" + + @property + def unique_id(self): + """Return unique ID for filter open state sensor on device.""" + return f"{super().unique_id}-filter-open-state" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} filter open state" + + @property + def is_on(self) -> bool: + """Return a value indicating whether the Humidifier's filter is open.""" + return self.smarthumidifier.details["filter_open_state"] diff --git a/custom_components/vesync/common.py b/custom_components/vesync/common.py index 02c4784..96ba099 100644 --- a/custom_components/vesync/common.py +++ b/custom_components/vesync/common.py @@ -146,7 +146,7 @@ def unique_id(self): @property def base_name(self): """Return the name of the device.""" - return self.device.device_type + return self.device.device_name @property def name(self): diff --git a/custom_components/vesync/const.py b/custom_components/vesync/const.py index 96fe288..e8768c4 100644 --- a/custom_components/vesync/const.py +++ b/custom_components/vesync/const.py @@ -25,10 +25,11 @@ VS_MODE_HUMIDITY = "humidity" VS_MODE_MANUAL = "manual" VS_MODE_SLEEP = "sleep" +VS_MODE_TURBO = "turbo" VS_TO_HA_ATTRIBUTES = {"humidity": "current_humidity"} -VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncVital"] +VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncVital", "VeSyncAirBaseV2"] VS_HUMIDIFIERS_TYPES = ["VeSyncHumid200300S", "VeSyncHumid200S", "VeSyncHumid1000S"] VS_AIRFRYER_TYPES = ["VeSyncAirFryer158"] diff --git a/custom_components/vesync/fan.py b/custom_components/vesync/fan.py index 25de507..a9b7f06 100644 --- a/custom_components/vesync/fan.py +++ b/custom_components/vesync/fan.py @@ -22,6 +22,7 @@ VS_MODE_AUTO, VS_MODE_MANUAL, VS_MODE_SLEEP, + VS_MODE_TURBO, VS_MODES, VS_TO_HA_ATTRIBUTES, ) @@ -69,15 +70,15 @@ def __init__(self, fan, coordinator) -> None: self.smartfan = fan self._speed_range = (1, 1) self._attr_preset_modes = [VS_MODE_MANUAL, VS_MODE_AUTO, VS_MODE_SLEEP] - if has_feature(self.smartfan, "config_dict", VS_LEVELS): - self._speed_range = (1, max(self.smartfan.config_dict[VS_LEVELS])) - if has_feature(self.smartfan, "config_dict", VS_MODES): + if has_feature(self.smartfan, "_config_dict", VS_LEVELS): + self._speed_range = (1, max(self.smartfan._config_dict[VS_LEVELS])) + if has_feature(self.smartfan, "_config_dict", VS_MODES): self._attr_preset_modes = [ VS_MODE_MANUAL, *[ mode - for mode in [VS_MODE_AUTO, VS_MODE_SLEEP] - if mode in self.smartfan.config_dict[VS_MODES] + for mode in [VS_MODE_AUTO, VS_MODE_SLEEP, VS_MODE_TURBO] + if mode in self.smartfan._config_dict[VS_MODES] ], ] if self.smartfan.device_type == "LV-PUR131S": @@ -161,6 +162,8 @@ def set_preset_mode(self, preset_mode): self.smartfan.sleep_mode() elif preset_mode == VS_MODE_MANUAL: self.smartfan.manual_mode() + elif preset_mode == VS_MODE_TURBO: + self.smartfan.turbo_mode() self.schedule_update_ha_state() diff --git a/custom_components/vesync/light.py b/custom_components/vesync/light.py index 59ab5e5..19d7eb2 100644 --- a/custom_components/vesync/light.py +++ b/custom_components/vesync/light.py @@ -255,7 +255,7 @@ def entity_category(self): def turn_on(self, **kwargs): """Turn the night light on.""" - if self.device.config_dict["module"] == "VeSyncAirBypass": + if self.device._config_dict["module"] == "VeSyncAirBypass": if ATTR_BRIGHTNESS in kwargs and kwargs[ATTR_BRIGHTNESS] < 255: self.device.set_night_light("dim") else: @@ -269,7 +269,7 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Turn the night light off.""" - if self.device.config_dict["module"] == "VeSyncAirBypass": + if self.device._config_dict["module"] == "VeSyncAirBypass": self.device.set_night_light("off") else: self.device.set_night_light_brightness(0) diff --git a/custom_components/vesync/number.py b/custom_components/vesync/number.py index 7a4cd53..3be0603 100644 --- a/custom_components/vesync/number.py +++ b/custom_components/vesync/number.py @@ -52,7 +52,7 @@ def _setup_entities(devices, async_add_entities, coordinator): entities.append(VeSyncHumidifierTargetLevelHA(dev, coordinator)) if has_feature(dev, "details", "warm_mist_level"): entities.append(VeSyncHumidifierWarmthLevelHA(dev, coordinator)) - if has_feature(dev, "config_dict", "levels"): + if has_feature(dev, "_config_dict", "levels"): entities.append(VeSyncFanSpeedLevelHA(dev, coordinator)) async_add_entities(entities, update_before_add=True) @@ -77,8 +77,8 @@ class VeSyncFanSpeedLevelHA(VeSyncNumberEntity): def __init__(self, device, coordinator) -> None: """Initialize the number entity.""" super().__init__(device, coordinator) - self._attr_native_min_value = device.config_dict["levels"][0] - self._attr_native_max_value = device.config_dict["levels"][-1] + self._attr_native_min_value = device._config_dict["levels"][0] + self._attr_native_max_value = device._config_dict["levels"][-1] self._attr_native_step = 1 @property @@ -99,7 +99,7 @@ def native_value(self): @property def extra_state_attributes(self): """Return the state attributes of the humidifier.""" - return {"fan speed levels": self.device.config_dict["levels"]} + return {"fan speed levels": self.device._config_dict["levels"]} def set_native_value(self, value): """Set the fan speed level.""" diff --git a/custom_components/vesync/sensor.py b/custom_components/vesync/sensor.py index 4ce8198..bcd9b16 100644 --- a/custom_components/vesync/sensor.py +++ b/custom_components/vesync/sensor.py @@ -8,7 +8,7 @@ SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower +from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, DEGREE from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory @@ -77,10 +77,18 @@ def _setup_entities(devices, async_add_entities, coordinator): entities.append(VeSyncHumiditySensor(dev, coordinator)) if has_feature(dev, "details", "air_quality"): entities.append(VeSyncAirQualitySensor(dev, coordinator)) + if has_feature(dev, "details", "aq_percent"): + entities.append(VeSyncAirQualityPercSensor(dev, coordinator)) if has_feature(dev, "details", "air_quality_value"): entities.append(VeSyncAirQualityValueSensor(dev, coordinator)) + if has_feature(dev, "details", "pm1"): + entities.append(VeSyncPM1Sensor(dev, coordinator)) + if has_feature(dev, "details", "pm10"): + entities.append(VeSyncPM10Sensor(dev, coordinator)) if has_feature(dev, "details", "filter_life"): entities.append(VeSyncFilterLifeSensor(dev, coordinator)) + if has_feature(dev, "details", "fan_rotate_angle"): + entities.append(VeSyncFanRotateAngleSensor(dev, coordinator)) async_add_entities(entities, update_before_add=True) @@ -238,14 +246,13 @@ def __init__(self, humidifier, coordinator) -> None: @property def entity_category(self): """Return the diagnostic entity category.""" - return EntityCategory.DIAGNOSTIC + return None class VeSyncAirQualitySensor(VeSyncHumidifierSensorEntity): """Representation of an air quality sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT - _attr_native_unit_of_measurement = " " def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" @@ -262,12 +269,12 @@ def device_class(self): @property def unique_id(self): """Return unique ID for air quality sensor on device.""" - return f"{super().unique_id}-air-quality" + return f"{super().unique_id}-air-quality-index" @property def name(self): """Return sensor name.""" - return f"{super().name} air quality" + return f"{super().name} air quality index" @property def native_value(self): @@ -284,13 +291,55 @@ def native_value(self): _LOGGER.warning("No air quality index found in '%s'", self.name) return None +class VeSyncAirQualityPercSensor(VeSyncHumidifierSensorEntity): + """Representation of an air quality percentage sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_device_class = SensorDeviceClass.AQI + + def __init__(self, device, coordinator) -> None: + """Initialize the VeSync device.""" + super().__init__(device, coordinator) + self._numeric_quality = None + if self.native_value is not None: + self._numeric_quality = isinstance(self.native_value, (int, float)) + + @property + def unique_id(self): + """Return unique ID for air quality sensor on device.""" + return f"{super().unique_id}-air-quality" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} air quality" + + @property + def native_unit_of_measurement(self): + """Return the % unit of measurement.""" + return PERCENTAGE + + @property + def native_value(self): + """Return the air quality percentage.""" + if has_feature(self.smarthumidifier, "details", "aq_percent"): + quality = self.smarthumidifier.details["aq_percent"] + if isinstance(quality, (int, float)): + return quality + _LOGGER.warning( + "Got non numeric value for AQI sensor from 'aq_percent' for %s: %s", + self.name, + quality, + ) + _LOGGER.warning("No air quality percentage found in '%s'", self.name) + return None class VeSyncAirQualityValueSensor(VeSyncHumidifierSensorEntity): """Representation of an air quality sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT - _attr_device_class = SensorDeviceClass.AQI - _attr_native_unit_of_measurement = " " + _attr_device_class = SensorDeviceClass.PM25 + _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" @@ -299,12 +348,12 @@ def __init__(self, device, coordinator) -> None: @property def unique_id(self): """Return unique ID for air quality sensor on device.""" - return f"{super().unique_id}-air-quality-value" + return f"{super().unique_id}-pm25" @property def name(self): """Return sensor name.""" - return f"{super().name} air quality value" + return f"{super().name} PM2.5" @property def native_value(self): @@ -321,14 +370,90 @@ def native_value(self): _LOGGER.warning("No air quality value found in '%s'", self.name) return None +class VeSyncPM1Sensor(VeSyncHumidifierSensorEntity): + """Representation of a PM1 sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_device_class = SensorDeviceClass.PM1 + _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + + def __init__(self, device, coordinator) -> None: + """Initialize the VeSync device.""" + super().__init__(device, coordinator) + + @property + def unique_id(self): + """Return unique ID for PM1 sensor on device.""" + return f"{super().unique_id}-pm1" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} PM1" + + @property + def native_value(self): + """Return the PM1""" + if has_feature(self.smarthumidifier, "details", "pm1"): + quality_value = self.smarthumidifier.details["pm1"] + if isinstance(quality_value, (int, float)): + return quality_value + _LOGGER.warning( + "Got non numeric value for PM1 sensor from 'pm1' for %s: %s", + self.name, + quality_value, + ) + _LOGGER.warning("No PM1 value found in '%s'", self.name) + return None + +class VeSyncPM10Sensor(VeSyncHumidifierSensorEntity): + """Representation of a PM10 sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_device_class = SensorDeviceClass.PM10 + _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + + def __init__(self, device, coordinator) -> None: + """Initialize the VeSync device.""" + super().__init__(device, coordinator) + + @property + def unique_id(self): + """Return unique ID for PM10 sensor on device.""" + return f"{super().unique_id}-pm10" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} PM10" + + @property + def native_value(self): + """Return the PM10""" + if has_feature(self.smarthumidifier, "details", "pm10"): + quality_value = self.smarthumidifier.details["pm10"] + if isinstance(quality_value, (int, float)): + return quality_value + _LOGGER.warning( + "Got non numeric value for PM10 sensor from 'pm10' for %s: %s", + self.name, + quality_value, + ) + _LOGGER.warning("No PM10 value found in '%s'", self.name) + return None class VeSyncFilterLifeSensor(VeSyncHumidifierSensorEntity): """Representation of a filter life sensor.""" def __init__(self, plug, coordinator) -> None: - """Initialize the VeSync outlet device.""" + """Initialize the VeSync device.""" super().__init__(plug, coordinator) + @property + def entity_category(self): + """Return the diagnostic entity category.""" + return EntityCategory.DIAGNOSTIC + @property def unique_id(self): """Return unique ID for filter life sensor on device.""" @@ -372,6 +497,61 @@ def state_attributes(self): else {} ) + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return "mdi:air-filter" + +class VeSyncFanRotateAngleSensor(VeSyncHumidifierSensorEntity): + """Representation of a fan rotate angle sensor.""" + + def __init__(self, plug, coordinator) -> None: + """Initialize the VeSync device.""" + super().__init__(plug, coordinator) + + @property + def entity_category(self): + """Return the diagnostic entity category.""" + return EntityCategory.DIAGNOSTIC + + @property + def unique_id(self): + """Return unique ID for filter life sensor on device.""" + return f"{super().unique_id}-fan-rotate-angle" + + @property + def name(self): + """Return sensor name.""" + return f"{super().name} fan rotate angle" + + @property + def device_class(self): + """Return the fan rotate angle device class.""" + return None + + @property + def native_value(self): + """Return the fan rotate angle index.""" + return ( + self.smarthumidifier.fan_rotate_angle + if hasattr(self.smarthumidifier, "fan_rotate_angle") + else self.smarthumidifier.details["fan_rotate_angle"] + ) + + @property + def native_unit_of_measurement(self): + """Return the % unit of measurement.""" + return DEGREE + + @property + def state_class(self): + """Return the measurement state class.""" + return SensorStateClass.MEASUREMENT + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return "mdi:rotate-3d-variant" class VeSyncHumiditySensor(VeSyncHumidifierSensorEntity): """Representation of current humidity for a VeSync humidifier.""" From 2fbdf8397cff7f3cb945415c85e5579d152a2aa3 Mon Sep 17 00:00:00 2001 From: HuffYk Date: Mon, 3 Jun 2024 16:44:01 +0200 Subject: [PATCH 2/5] using custom pyvesync (merged master & everest-air branches) --- custom_components/vesync/const.py | 2 +- custom_components/vesync/fan.py | 8 ++++---- custom_components/vesync/light.py | 6 +++--- custom_components/vesync/number.py | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/custom_components/vesync/const.py b/custom_components/vesync/const.py index e8768c4..7a0b63d 100644 --- a/custom_components/vesync/const.py +++ b/custom_components/vesync/const.py @@ -29,7 +29,7 @@ VS_TO_HA_ATTRIBUTES = {"humidity": "current_humidity"} -VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncVital", "VeSyncAirBaseV2"] +VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncVital"] VS_HUMIDIFIERS_TYPES = ["VeSyncHumid200300S", "VeSyncHumid200S", "VeSyncHumid1000S"] VS_AIRFRYER_TYPES = ["VeSyncAirFryer158"] diff --git a/custom_components/vesync/fan.py b/custom_components/vesync/fan.py index a9b7f06..289675e 100644 --- a/custom_components/vesync/fan.py +++ b/custom_components/vesync/fan.py @@ -70,15 +70,15 @@ def __init__(self, fan, coordinator) -> None: self.smartfan = fan self._speed_range = (1, 1) self._attr_preset_modes = [VS_MODE_MANUAL, VS_MODE_AUTO, VS_MODE_SLEEP] - if has_feature(self.smartfan, "_config_dict", VS_LEVELS): - self._speed_range = (1, max(self.smartfan._config_dict[VS_LEVELS])) - if has_feature(self.smartfan, "_config_dict", VS_MODES): + if has_feature(self.smartfan, "config_dict", VS_LEVELS): + self._speed_range = (1, max(self.smartfan.config_dict[VS_LEVELS])) + if has_feature(self.smartfan, "config_dict", VS_MODES): self._attr_preset_modes = [ VS_MODE_MANUAL, *[ mode for mode in [VS_MODE_AUTO, VS_MODE_SLEEP, VS_MODE_TURBO] - if mode in self.smartfan._config_dict[VS_MODES] + if mode in self.smartfan.config_dict[VS_MODES] ], ] if self.smartfan.device_type == "LV-PUR131S": diff --git a/custom_components/vesync/light.py b/custom_components/vesync/light.py index 19d7eb2..d6fea9a 100644 --- a/custom_components/vesync/light.py +++ b/custom_components/vesync/light.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .common import VeSyncDevice, has_feature -from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS +from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS, VS_FAN_TYPES _LOGGER = logging.getLogger(__name__) @@ -255,7 +255,7 @@ def entity_category(self): def turn_on(self, **kwargs): """Turn the night light on.""" - if self.device._config_dict["module"] == "VeSyncAirBypass": + if self.device.config_dict["module"] in VS_FAN_TYPES: if ATTR_BRIGHTNESS in kwargs and kwargs[ATTR_BRIGHTNESS] < 255: self.device.set_night_light("dim") else: @@ -269,7 +269,7 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Turn the night light off.""" - if self.device._config_dict["module"] == "VeSyncAirBypass": + if self.device.config_dict["module"] in VS_FAN_TYPES: self.device.set_night_light("off") else: self.device.set_night_light_brightness(0) diff --git a/custom_components/vesync/number.py b/custom_components/vesync/number.py index 3be0603..7a4cd53 100644 --- a/custom_components/vesync/number.py +++ b/custom_components/vesync/number.py @@ -52,7 +52,7 @@ def _setup_entities(devices, async_add_entities, coordinator): entities.append(VeSyncHumidifierTargetLevelHA(dev, coordinator)) if has_feature(dev, "details", "warm_mist_level"): entities.append(VeSyncHumidifierWarmthLevelHA(dev, coordinator)) - if has_feature(dev, "_config_dict", "levels"): + if has_feature(dev, "config_dict", "levels"): entities.append(VeSyncFanSpeedLevelHA(dev, coordinator)) async_add_entities(entities, update_before_add=True) @@ -77,8 +77,8 @@ class VeSyncFanSpeedLevelHA(VeSyncNumberEntity): def __init__(self, device, coordinator) -> None: """Initialize the number entity.""" super().__init__(device, coordinator) - self._attr_native_min_value = device._config_dict["levels"][0] - self._attr_native_max_value = device._config_dict["levels"][-1] + self._attr_native_min_value = device.config_dict["levels"][0] + self._attr_native_max_value = device.config_dict["levels"][-1] self._attr_native_step = 1 @property @@ -99,7 +99,7 @@ def native_value(self): @property def extra_state_attributes(self): """Return the state attributes of the humidifier.""" - return {"fan speed levels": self.device._config_dict["levels"]} + return {"fan speed levels": self.device.config_dict["levels"]} def set_native_value(self, value): """Set the fan speed level.""" From 0f2fd2f88099fb36cd10bb0c29bfe0530f2809c0 Mon Sep 17 00:00:00 2001 From: HuffYk Date: Wed, 26 Jun 2024 19:22:26 +0200 Subject: [PATCH 3/5] remove AQI device class for air quality percentage sensor --- custom_components/vesync/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/vesync/sensor.py b/custom_components/vesync/sensor.py index bcd9b16..d4ee86b 100644 --- a/custom_components/vesync/sensor.py +++ b/custom_components/vesync/sensor.py @@ -295,7 +295,6 @@ class VeSyncAirQualityPercSensor(VeSyncHumidifierSensorEntity): """Representation of an air quality percentage sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT - _attr_device_class = SensorDeviceClass.AQI def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" From 077a7cf1f753ea52f599b8b883a9223a01a17368 Mon Sep 17 00:00:00 2001 From: HuffYk Date: Sun, 30 Jun 2024 14:41:33 +0200 Subject: [PATCH 4/5] update to latest official pyvesync --- custom_components/vesync/binary_sensor.py | 1 + custom_components/vesync/const.py | 2 +- custom_components/vesync/fan.py | 8 ++--- custom_components/vesync/light.py | 6 ++-- custom_components/vesync/manifest.json | 4 +-- custom_components/vesync/number.py | 20 ++++++------ custom_components/vesync/sensor.py | 37 +++++++++++++++-------- requirements.txt | 2 +- 8 files changed, 47 insertions(+), 33 deletions(-) diff --git a/custom_components/vesync/binary_sensor.py b/custom_components/vesync/binary_sensor.py index 4236bf1..d0d373a 100644 --- a/custom_components/vesync/binary_sensor.py +++ b/custom_components/vesync/binary_sensor.py @@ -151,6 +151,7 @@ def is_on(self) -> bool: """Return a value indicating whether the Humidifier's water tank is lifted.""" return self.smarthumidifier.details["water_tank_lifted"] + class VeSyncFilterOpenStateSensor(VeSyncBinarySensorEntity): """Filter Open Sensor.""" diff --git a/custom_components/vesync/const.py b/custom_components/vesync/const.py index 7a0b63d..361e28b 100644 --- a/custom_components/vesync/const.py +++ b/custom_components/vesync/const.py @@ -29,7 +29,7 @@ VS_TO_HA_ATTRIBUTES = {"humidity": "current_humidity"} -VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncVital"] +VS_FAN_TYPES = ["VeSyncAirBypass", "VeSyncAir131", "VeSyncAirBaseV2"] VS_HUMIDIFIERS_TYPES = ["VeSyncHumid200300S", "VeSyncHumid200S", "VeSyncHumid1000S"] VS_AIRFRYER_TYPES = ["VeSyncAirFryer158"] diff --git a/custom_components/vesync/fan.py b/custom_components/vesync/fan.py index 289675e..a9b7f06 100644 --- a/custom_components/vesync/fan.py +++ b/custom_components/vesync/fan.py @@ -70,15 +70,15 @@ def __init__(self, fan, coordinator) -> None: self.smartfan = fan self._speed_range = (1, 1) self._attr_preset_modes = [VS_MODE_MANUAL, VS_MODE_AUTO, VS_MODE_SLEEP] - if has_feature(self.smartfan, "config_dict", VS_LEVELS): - self._speed_range = (1, max(self.smartfan.config_dict[VS_LEVELS])) - if has_feature(self.smartfan, "config_dict", VS_MODES): + if has_feature(self.smartfan, "_config_dict", VS_LEVELS): + self._speed_range = (1, max(self.smartfan._config_dict[VS_LEVELS])) + if has_feature(self.smartfan, "_config_dict", VS_MODES): self._attr_preset_modes = [ VS_MODE_MANUAL, *[ mode for mode in [VS_MODE_AUTO, VS_MODE_SLEEP, VS_MODE_TURBO] - if mode in self.smartfan.config_dict[VS_MODES] + if mode in self.smartfan._config_dict[VS_MODES] ], ] if self.smartfan.device_type == "LV-PUR131S": diff --git a/custom_components/vesync/light.py b/custom_components/vesync/light.py index d6fea9a..73d0160 100644 --- a/custom_components/vesync/light.py +++ b/custom_components/vesync/light.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .common import VeSyncDevice, has_feature -from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS, VS_FAN_TYPES +from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_FAN_TYPES, VS_LIGHTS _LOGGER = logging.getLogger(__name__) @@ -255,7 +255,7 @@ def entity_category(self): def turn_on(self, **kwargs): """Turn the night light on.""" - if self.device.config_dict["module"] in VS_FAN_TYPES: + if self.device._config_dict["module"] in VS_FAN_TYPES: if ATTR_BRIGHTNESS in kwargs and kwargs[ATTR_BRIGHTNESS] < 255: self.device.set_night_light("dim") else: @@ -269,7 +269,7 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Turn the night light off.""" - if self.device.config_dict["module"] in VS_FAN_TYPES: + if self.device._config_dict["module"] in VS_FAN_TYPES: self.device.set_night_light("off") else: self.device.set_night_light_brightness(0) diff --git a/custom_components/vesync/manifest.json b/custom_components/vesync/manifest.json index 3e63437..6c706c8 100644 --- a/custom_components/vesync/manifest.json +++ b/custom_components/vesync/manifest.json @@ -19,7 +19,7 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/AndreaTomatis/custom_vesync", "requirements": [ - "pyvesync==2.1.10" + "pyvesync==2.1.12" ], - "version": "1.3.1" + "version": "1.3.2" } \ No newline at end of file diff --git a/custom_components/vesync/number.py b/custom_components/vesync/number.py index 7a4cd53..5074932 100644 --- a/custom_components/vesync/number.py +++ b/custom_components/vesync/number.py @@ -52,7 +52,7 @@ def _setup_entities(devices, async_add_entities, coordinator): entities.append(VeSyncHumidifierTargetLevelHA(dev, coordinator)) if has_feature(dev, "details", "warm_mist_level"): entities.append(VeSyncHumidifierWarmthLevelHA(dev, coordinator)) - if has_feature(dev, "config_dict", "levels"): + if has_feature(dev, "_config_dict", "levels"): entities.append(VeSyncFanSpeedLevelHA(dev, coordinator)) async_add_entities(entities, update_before_add=True) @@ -77,8 +77,8 @@ class VeSyncFanSpeedLevelHA(VeSyncNumberEntity): def __init__(self, device, coordinator) -> None: """Initialize the number entity.""" super().__init__(device, coordinator) - self._attr_native_min_value = device.config_dict["levels"][0] - self._attr_native_max_value = device.config_dict["levels"][-1] + self._attr_native_min_value = device._config_dict["levels"][0] + self._attr_native_max_value = device._config_dict["levels"][-1] self._attr_native_step = 1 @property @@ -99,7 +99,7 @@ def native_value(self): @property def extra_state_attributes(self): """Return the state attributes of the humidifier.""" - return {"fan speed levels": self.device.config_dict["levels"]} + return {"fan speed levels": self.device._config_dict["levels"]} def set_native_value(self, value): """Set the fan speed level.""" @@ -112,8 +112,8 @@ class VeSyncHumidifierMistLevelHA(VeSyncNumberEntity): def __init__(self, device, coordinator) -> None: """Initialize the number entity.""" super().__init__(device, coordinator) - self._attr_native_min_value = device.config_dict["mist_levels"][0] - self._attr_native_max_value = device.config_dict["mist_levels"][-1] + self._attr_native_min_value = device._config_dict["mist_levels"][0] + self._attr_native_max_value = device._config_dict["mist_levels"][-1] self._attr_native_step = 1 @property @@ -134,7 +134,7 @@ def native_value(self): @property def extra_state_attributes(self): """Return the state attributes of the humidifier.""" - return {"mist levels": self.device.config_dict["mist_levels"]} + return {"mist levels": self.device._config_dict["mist_levels"]} def set_native_value(self, value): """Set the mist level.""" @@ -147,8 +147,8 @@ class VeSyncHumidifierWarmthLevelHA(VeSyncNumberEntity): def __init__(self, device, coordinator) -> None: """Initialize the number entity.""" super().__init__(device, coordinator) - self._attr_native_min_value = device.config_dict["warm_mist_levels"][0] - self._attr_native_max_value = device.config_dict["warm_mist_levels"][-1] + self._attr_native_min_value = device._config_dict["warm_mist_levels"][0] + self._attr_native_max_value = device._config_dict["warm_mist_levels"][-1] self._attr_native_step = 1 @property @@ -169,7 +169,7 @@ def native_value(self): @property def extra_state_attributes(self): """Return the state attributes of the humidifier.""" - return {"warm mist levels": self.device.config_dict["warm_mist_levels"]} + return {"warm mist levels": self.device._config_dict["warm_mist_levels"]} def set_native_value(self, value): """Set the mist level.""" diff --git a/custom_components/vesync/sensor.py b/custom_components/vesync/sensor.py index d4ee86b..2e75152 100644 --- a/custom_components/vesync/sensor.py +++ b/custom_components/vesync/sensor.py @@ -8,7 +8,13 @@ SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, DEGREE +from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + DEGREE, + PERCENTAGE, + UnitOfEnergy, + UnitOfPower, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory @@ -269,12 +275,12 @@ def device_class(self): @property def unique_id(self): """Return unique ID for air quality sensor on device.""" - return f"{super().unique_id}-air-quality-index" + return f"{super().unique_id}-air-quality" @property def name(self): """Return sensor name.""" - return f"{super().name} air quality index" + return f"{super().name} air quality" @property def native_value(self): @@ -291,6 +297,7 @@ def native_value(self): _LOGGER.warning("No air quality index found in '%s'", self.name) return None + class VeSyncAirQualityPercSensor(VeSyncHumidifierSensorEntity): """Representation of an air quality percentage sensor.""" @@ -306,12 +313,12 @@ def __init__(self, device, coordinator) -> None: @property def unique_id(self): """Return unique ID for air quality sensor on device.""" - return f"{super().unique_id}-air-quality" + return f"{super().unique_id}-air-quality-perc" @property def name(self): """Return sensor name.""" - return f"{super().name} air quality" + return f"{super().name} air quality percentage" @property def native_unit_of_measurement(self): @@ -333,12 +340,13 @@ def native_value(self): _LOGGER.warning("No air quality percentage found in '%s'", self.name) return None + class VeSyncAirQualityValueSensor(VeSyncHumidifierSensorEntity): """Representation of an air quality sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT _attr_device_class = SensorDeviceClass.PM25 - _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + _attr_native_unit_of_measurement = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" @@ -347,12 +355,12 @@ def __init__(self, device, coordinator) -> None: @property def unique_id(self): """Return unique ID for air quality sensor on device.""" - return f"{super().unique_id}-pm25" + return f"{super().unique_id}-air-quality-value" @property def name(self): """Return sensor name.""" - return f"{super().name} PM2.5" + return f"{super().name} air quality value" @property def native_value(self): @@ -369,12 +377,13 @@ def native_value(self): _LOGGER.warning("No air quality value found in '%s'", self.name) return None + class VeSyncPM1Sensor(VeSyncHumidifierSensorEntity): """Representation of a PM1 sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT _attr_device_class = SensorDeviceClass.PM1 - _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + _attr_native_unit_of_measurement = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" @@ -392,7 +401,7 @@ def name(self): @property def native_value(self): - """Return the PM1""" + """Return the PM1.""" if has_feature(self.smarthumidifier, "details", "pm1"): quality_value = self.smarthumidifier.details["pm1"] if isinstance(quality_value, (int, float)): @@ -405,12 +414,13 @@ def native_value(self): _LOGGER.warning("No PM1 value found in '%s'", self.name) return None + class VeSyncPM10Sensor(VeSyncHumidifierSensorEntity): """Representation of a PM10 sensor.""" _attr_state_class = SensorStateClass.MEASUREMENT _attr_device_class = SensorDeviceClass.PM10 - _attr_native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + _attr_native_unit_of_measurement = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER def __init__(self, device, coordinator) -> None: """Initialize the VeSync device.""" @@ -428,7 +438,7 @@ def name(self): @property def native_value(self): - """Return the PM10""" + """Return the PM10.""" if has_feature(self.smarthumidifier, "details", "pm10"): quality_value = self.smarthumidifier.details["pm10"] if isinstance(quality_value, (int, float)): @@ -441,6 +451,7 @@ def native_value(self): _LOGGER.warning("No PM10 value found in '%s'", self.name) return None + class VeSyncFilterLifeSensor(VeSyncHumidifierSensorEntity): """Representation of a filter life sensor.""" @@ -501,6 +512,7 @@ def icon(self): """Return the icon to use in the frontend, if any.""" return "mdi:air-filter" + class VeSyncFanRotateAngleSensor(VeSyncHumidifierSensorEntity): """Representation of a fan rotate angle sensor.""" @@ -552,6 +564,7 @@ def icon(self): """Return the icon to use in the frontend, if any.""" return "mdi:rotate-3d-variant" + class VeSyncHumiditySensor(VeSyncHumidifierSensorEntity): """Representation of current humidity for a VeSync humidifier.""" diff --git a/requirements.txt b/requirements.txt index 3dd8ebd..1f0f6a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pyvesync==2.1.10 +pyvesync==2.1.12 From a2bddeee1b8090a3fac9eca3bb2a65a057609f53 Mon Sep 17 00:00:00 2001 From: HuffYk Date: Sun, 30 Jun 2024 17:57:03 +0200 Subject: [PATCH 5/5] corrected documentation & issue tracker links --- custom_components/vesync/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/vesync/manifest.json b/custom_components/vesync/manifest.json index 6c706c8..1a041b6 100644 --- a/custom_components/vesync/manifest.json +++ b/custom_components/vesync/manifest.json @@ -15,9 +15,9 @@ "macaddress": "*" } ], - "documentation": "https://github.com/AndreaTomatis/custom_vesync", + "documentation": "https://github.com/micahqcade/custom_vesync", "iot_class": "cloud_polling", - "issue_tracker": "https://github.com/AndreaTomatis/custom_vesync", + "issue_tracker": "https://github.com/micahqcade/custom_vesync", "requirements": [ "pyvesync==2.1.12" ],