Report the running VeSync purifier fan level in preset modes (#183014)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Ryan Ronnander
2026-09-24 16:12:31 +01:00
committed by GitHub
co-authored by Joostlek
parent 57692a9116
commit fe42af42c9
11 changed files with 1059 additions and 358 deletions
+16 -10
View File
@@ -4,6 +4,7 @@ import logging
from typing import Any, cast, override
from pyvesync.base_devices import VeSyncFanBase, VeSyncPurifier
from pyvesync.devices.vesyncpurifier import VeSyncAirBaseV2
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.core import HomeAssistant, callback
@@ -162,18 +163,23 @@ class VeSyncFanHA(VeSyncBaseEntity[VeSyncFanBase | VeSyncPurifier], FanEntity):
@property
@override
def percentage(self) -> int | None:
"""Return the currently set speed."""
"""Return the current speed."""
current_level = self.device.state.fan_level
if self.device.state.mode in (VS_FAN_MODE_MANUAL, VS_FAN_MODE_NORMAL):
if current_level == 0:
return 0
# The device can report an out-of-range level (e.g. -1) when the
# speed is not applicable; treat it as unknown instead of crashing.
if current_level in self.device.fan_levels:
return ordered_list_item_to_percentage(
self.device.fan_levels, current_level
)
in_manual_mode = self.device.state.mode in (
VS_FAN_MODE_MANUAL,
VS_FAN_MODE_NORMAL,
)
# The legacy purifier API reports the manual setting, not the running level
if not in_manual_mode and not isinstance(self.device, VeSyncAirBaseV2):
return None
if current_level in self.device.fan_levels:
return ordered_list_item_to_percentage(
self.device.fan_levels, current_level
)
if current_level == 0 and in_manual_mode:
return 0
# Unknown or out-of-range level (e.g. -1 or the 255 sentinel)
return None
@property
+3
View File
@@ -56,6 +56,9 @@ DEVICE_FIXTURES: dict[str, list[tuple[str, str, str]]] = {
"Air Purifier 600s": [
("post", "/cloud/v2/deviceManaged/bypassV2", "air-purifier-detail.json")
],
"Air Purifier Vital 200S": [
("post", "/cloud/v2/deviceManaged/bypassV2", "air-purifier-detail-v2.json")
],
"Dimmable Light": [
("post", "/cloud/v1/deviceManaged/deviceDetail", "dimmable-light-detail.json")
],
@@ -296,6 +296,21 @@
"connectionStatus": "online",
"uuid": "00000000-1111-2222-3333-555555555555",
"configModule": "configModule"
},
{
"deviceRegion": "US",
"isOwner": true,
"cid": "vital200s-purifier",
"deviceType": "LAP-V201S-WUS",
"deviceName": "Air Purifier Vital 200S",
"subDeviceNo": null,
"deviceStatus": "on",
"deviceImg": "",
"type": "wifi-air",
"connectionType": "",
"connectionStatus": "online",
"uuid": "00000000-1111-2222-3333-666666666666",
"configModule": "configModule"
}
]
}
@@ -143,6 +143,42 @@
list([
])
# ---
# name: test_sensor_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[Air Purifier Vital 200S][entities]
list([
])
# ---
# name: test_sensor_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -215,6 +251,42 @@
list([
])
# ---
# name: test_sensor_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[CoreBreeze 432S][entities]
list([
])
# ---
# name: test_sensor_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -833,39 +905,3 @@
list([
])
# ---
# name: test_sensor_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[CoreBreeze 432S][entities]
list([
])
# ---
+209 -106
View File
@@ -403,6 +403,109 @@
'state': 'on',
})
# ---
# name: test_fan_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_fan_state[Air Purifier Vital 200S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'auto',
'pet',
'sleep',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.air_purifier_vital_200s',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 57>,
'translation_key': 'vesync',
'unique_id': 'vital200s-purifier',
'unit_of_measurement': None,
}),
])
# ---
# name: test_fan_state[Air Purifier Vital 200S][fan.air_purifier_vital_200s]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_time': None,
'child_lock': False,
'display_status': 'on',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S',
'mode': 'manual',
<FanEntityStateAttribute.PERCENTAGE: 'percentage'>: 0,
<FanEntityStateAttribute.PERCENTAGE_STEP: 'percentage_step'>: 25.0,
<FanEntityStateAttribute.PRESET_MODE: 'preset_mode'>: None,
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'auto',
'pet',
'sleep',
]),
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <FanEntityFeature: 57>,
}),
'context': <ANY>,
'entity_id': 'fan.air_purifier_vital_200s',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_fan_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -475,6 +578,112 @@
list([
])
# ---
# name: test_fan_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_fan_state[CoreBreeze 432S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'eco',
'normal',
'sleep',
'turbo',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.corebreeze_432s',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 59>,
'translation_key': 'vesync',
'unique_id': 'corebreeze432s',
'unit_of_measurement': None,
}),
])
# ---
# name: test_fan_state[CoreBreeze 432S][fan.corebreeze_432s]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_time': None,
'child_lock': False,
'display_status': 'off',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CoreBreeze 432S',
'mode': <FanModes.NORMAL: 'normal'>,
<FanEntityStateAttribute.OSCILLATING: 'oscillating'>: True,
<FanEntityStateAttribute.PERCENTAGE: 'percentage'>: 8,
<FanEntityStateAttribute.PERCENTAGE_STEP: 'percentage_step'>: 8.333333333333334,
<FanEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'normal',
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'eco',
'normal',
'sleep',
'turbo',
]),
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <FanEntityFeature: 59>,
}),
'context': <ANY>,
'entity_id': 'fan.corebreeze_432s',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_fan_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -868,109 +1077,3 @@
list([
])
# ---
# name: test_fan_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_fan_state[CoreBreeze 432S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'eco',
'normal',
'sleep',
'turbo',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.corebreeze_432s',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <FanEntityFeature: 59>,
'translation_key': 'vesync',
'unique_id': 'corebreeze432s',
'unit_of_measurement': None,
}),
])
# ---
# name: test_fan_state[CoreBreeze 432S][fan.corebreeze_432s]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_time': None,
'child_lock': False,
'display_status': 'off',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CoreBreeze 432S',
'mode': <FanModes.NORMAL: 'normal'>,
<FanEntityStateAttribute.OSCILLATING: 'oscillating'>: True,
<FanEntityStateAttribute.PERCENTAGE: 'percentage'>: 8,
<FanEntityStateAttribute.PERCENTAGE_STEP: 'percentage_step'>: 8.333333333333334,
<FanEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'normal',
<FanEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
'eco',
'normal',
'sleep',
'turbo',
]),
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <FanEntityFeature: 59>,
}),
'context': <ANY>,
'entity_id': 'fan.corebreeze_432s',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
@@ -143,6 +143,42 @@
list([
])
# ---
# name: test_humidifier_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_humidifier_state[Air Purifier Vital 200S][entities]
list([
])
# ---
# name: test_humidifier_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -215,6 +251,42 @@
list([
])
# ---
# name: test_humidifier_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_humidifier_state[CoreBreeze 432S][entities]
list([
])
# ---
# name: test_humidifier_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -740,39 +812,3 @@
list([
])
# ---
# name: test_humidifier_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_humidifier_state[CoreBreeze 432S][entities]
list([
])
# ---
@@ -143,6 +143,42 @@
list([
])
# ---
# name: test_light_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_light_state[Air Purifier Vital 200S][entities]
list([
])
# ---
# name: test_light_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -215,6 +251,42 @@
list([
])
# ---
# name: test_light_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_light_state[CoreBreeze 432S][entities]
list([
])
# ---
# name: test_light_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -731,39 +803,3 @@
list([
])
# ---
# name: test_light_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_light_state[CoreBreeze 432S][entities]
list([
])
# ---
@@ -601,6 +601,195 @@
'state': '5',
})
# ---
# name: test_sensor_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[Air Purifier Vital 200S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_purifier_vital_200s_air_quality',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Air quality',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Air quality',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'air_quality',
'unique_id': 'vital200s-purifier-air-quality',
'unit_of_measurement': None,
}),
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.air_purifier_vital_200s_pm2_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'PM2.5',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
'original_icon': None,
'original_name': 'PM2.5',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'vital200s-purifier-pm25',
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
}),
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.air_purifier_vital_200s_filter_lifetime',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Filter lifetime',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Filter lifetime',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'filter_life',
'unique_id': 'vital200s-purifier-filter-life',
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
}),
])
# ---
# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_air_quality]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S Air quality',
}),
'context': <ANY>,
'entity_id': 'sensor.air_purifier_vital_200s_air_quality',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'excellent',
})
# ---
# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_filter_lifetime]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S Filter lifetime',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_purifier_vital_200s_filter_lifetime',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '99',
})
# ---
# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_pm2_5]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S PM2.5',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
}),
'context': <ANY>,
'entity_id': 'sensor.air_purifier_vital_200s_pm2_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '5',
})
# ---
# name: test_sensor_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -1241,6 +1430,42 @@
'state': 'unknown',
})
# ---
# name: test_sensor_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[CoreBreeze 432S][entities]
list([
])
# ---
# name: test_sensor_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -2168,39 +2393,3 @@
list([
])
# ---
# name: test_sensor_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_sensor_state[CoreBreeze 432S][entities]
list([
])
# ---
@@ -479,6 +479,138 @@
'state': 'on',
})
# ---
# name: test_switch_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_switch_state[Air Purifier Vital 200S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.air_purifier_vital_200s_display',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Display',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Display',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'display',
'unique_id': 'vital200s-purifier-display',
'unit_of_measurement': None,
}),
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.air_purifier_vital_200s_child_lock',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Child lock',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Child lock',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'child_lock',
'unique_id': 'vital200s-purifier-child_lock',
'unit_of_measurement': None,
}),
])
# ---
# name: test_switch_state[Air Purifier Vital 200S][switch.air_purifier_vital_200s_child_lock]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S Child lock',
}),
'context': <ANY>,
'entity_id': 'switch.air_purifier_vital_200s_child_lock',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_state[Air Purifier Vital 200S][switch.air_purifier_vital_200s_display]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S Display',
}),
'context': <ANY>,
'entity_id': 'switch.air_purifier_vital_200s_display',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -387,6 +387,103 @@
'state': 'on',
})
# ---
# name: test_update_state[Air Purifier Vital 200S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'vital200s-purifier',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LAP-V201S-WUS',
'model_id': None,
'name': 'Air Purifier Vital 200S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_update_state[Air Purifier Vital 200S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.air_purifier_vital_200s_firmware',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Firmware',
'options': dict({
}),
'original_device_class': <UpdateDeviceClass.FIRMWARE: 'firmware'>,
'original_icon': None,
'original_name': 'Firmware',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'vital200s-purifier',
'unit_of_measurement': None,
}),
])
# ---
# name: test_update_state[Air Purifier Vital 200S][update.air_purifier_vital_200s_firmware]
StateSnapshot({
'attributes': ReadOnlyDict({
<UpdateEntityStateAttribute.AUTO_UPDATE: 'auto_update'>: False,
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'firmware',
<UpdateEntityStateAttribute.DISPLAY_PRECISION: 'display_precision'>: 0,
<EntityStateAttribute.ENTITY_PICTURE: 'entity_picture'>: '/api/brands/integration/vesync/icon.png',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Air Purifier Vital 200S Firmware',
<UpdateEntityStateAttribute.IN_PROGRESS: 'in_progress'>: False,
<UpdateEntityStateAttribute.INSTALLED_VERSION: 'installed_version'>: None,
<UpdateEntityStateAttribute.LATEST_VERSION: 'latest_version'>: None,
<UpdateEntityStateAttribute.RELEASE_SUMMARY: 'release_summary'>: None,
<UpdateEntityStateAttribute.RELEASE_URL: 'release_url'>: None,
<UpdateEntityStateAttribute.SKIPPED_VERSION: 'skipped_version'>: None,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <UpdateEntityFeature: 0>,
<UpdateEntityStateAttribute.TITLE: 'title'>: None,
<UpdateEntityStateAttribute.UPDATE_PERCENTAGE: 'update_percentage'>: None,
}),
'context': <ANY>,
'entity_id': 'update.air_purifier_vital_200s_firmware',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_update_state[CS158-AF Air Fryer Cooking][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -581,6 +678,103 @@
'state': 'unknown',
})
# ---
# name: test_update_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_update_state[CoreBreeze 432S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.corebreeze_432s_firmware',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Firmware',
'options': dict({
}),
'original_device_class': <UpdateDeviceClass.FIRMWARE: 'firmware'>,
'original_icon': None,
'original_name': 'Firmware',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'corebreeze432s',
'unit_of_measurement': None,
}),
])
# ---
# name: test_update_state[CoreBreeze 432S][update.corebreeze_432s_firmware]
StateSnapshot({
'attributes': ReadOnlyDict({
<UpdateEntityStateAttribute.AUTO_UPDATE: 'auto_update'>: False,
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'firmware',
<UpdateEntityStateAttribute.DISPLAY_PRECISION: 'display_precision'>: 0,
<EntityStateAttribute.ENTITY_PICTURE: 'entity_picture'>: '/api/brands/integration/vesync/icon.png',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CoreBreeze 432S Firmware',
<UpdateEntityStateAttribute.IN_PROGRESS: 'in_progress'>: False,
<UpdateEntityStateAttribute.INSTALLED_VERSION: 'installed_version'>: None,
<UpdateEntityStateAttribute.LATEST_VERSION: 'latest_version'>: None,
<UpdateEntityStateAttribute.RELEASE_SUMMARY: 'release_summary'>: None,
<UpdateEntityStateAttribute.RELEASE_URL: 'release_url'>: None,
<UpdateEntityStateAttribute.SKIPPED_VERSION: 'skipped_version'>: None,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <UpdateEntityFeature: 0>,
<UpdateEntityStateAttribute.TITLE: 'title'>: None,
<UpdateEntityStateAttribute.UPDATE_PERCENTAGE: 'update_percentage'>: None,
}),
'context': <ANY>,
'entity_id': 'update.corebreeze_432s_firmware',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_update_state[Dimmable Light][devices]
list([
DeviceRegistryEntrySnapshot({
@@ -1454,101 +1648,3 @@
'state': 'on',
})
# ---
# ---
# name: test_update_state[CoreBreeze 432S][devices]
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'vesync',
'corebreeze432s',
),
}),
'labels': set({
}),
'manufacturer': 'VeSync',
'model': 'LPF-R432S-AEU',
'model_id': None,
'name': 'CoreBreeze 432S',
'name_by_user': None,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---
# name: test_update_state[CoreBreeze 432S][entities]
list([
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.corebreeze_432s_firmware',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Firmware',
'options': dict({
}),
'original_device_class': <UpdateDeviceClass.FIRMWARE: 'firmware'>,
'original_icon': None,
'original_name': 'Firmware',
'platform': 'vesync',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'corebreeze432s',
'unit_of_measurement': None,
}),
])
# ---
# name: test_update_state[CoreBreeze 432S][update.corebreeze_432s_firmware]
StateSnapshot({
'attributes': ReadOnlyDict({
<UpdateEntityStateAttribute.AUTO_UPDATE: 'auto_update'>: False,
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'firmware',
<UpdateEntityStateAttribute.DISPLAY_PRECISION: 'display_precision'>: 0,
<EntityStateAttribute.ENTITY_PICTURE: 'entity_picture'>: '/api/brands/integration/vesync/icon.png',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CoreBreeze 432S Firmware',
<UpdateEntityStateAttribute.IN_PROGRESS: 'in_progress'>: False,
<UpdateEntityStateAttribute.INSTALLED_VERSION: 'installed_version'>: None,
<UpdateEntityStateAttribute.LATEST_VERSION: 'latest_version'>: None,
<UpdateEntityStateAttribute.RELEASE_SUMMARY: 'release_summary'>: None,
<UpdateEntityStateAttribute.RELEASE_URL: 'release_url'>: None,
<UpdateEntityStateAttribute.SKIPPED_VERSION: 'skipped_version'>: None,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <UpdateEntityFeature: 0>,
<UpdateEntityStateAttribute.TITLE: 'title'>: None,
<UpdateEntityStateAttribute.UPDATE_PERCENTAGE: 'update_percentage'>: None,
}),
'context': <ANY>,
'entity_id': 'update.corebreeze_432s_firmware',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
+49
View File
@@ -354,3 +354,52 @@ async def test_oscillation_success(
await hass.async_block_till_done()
method_mock.assert_called_once()
update_mock.assert_called_once()
@pytest.mark.parametrize(
("device_name", "entity_id", "details", "expected_percentage"),
[
(
"Air Purifier Vital 200S",
"fan.air_purifier_vital_200s",
{"workMode": "pet", "fanSpeedLevel": 3},
75,
),
(
"Air Purifier Vital 200S",
"fan.air_purifier_vital_200s",
{"workMode": "auto", "fanSpeedLevel": 2},
50,
),
(
"Air Purifier Vital 200S",
"fan.air_purifier_vital_200s",
{"workMode": "sleep", "fanSpeedLevel": 255},
None,
),
(
"Air Purifier 200s",
"fan.air_purifier_200s",
{"mode": "sleep", "level": 3},
None,
),
],
)
async def test_percentage_in_preset_mode(
hass: HomeAssistant,
config_entry: MockConfigEntry,
aioclient_mock: AiohttpClientMocker,
device_name: str,
entity_id: str,
details: dict[str, int | str],
expected_percentage: int | None,
) -> None:
"""Test the running level is reported in presets only where the API has it."""
mock_devices_response(aioclient_mock, device_name, details_override=details)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state is not None
assert state.attributes[ATTR_PERCENTAGE] == expected_percentage