From fe42af42c9031d53188a2c7d631f7d380a16c3be Mon Sep 17 00:00:00 2001 From: Ryan Ronnander <61520+ryan-ronnander@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:12:31 -0400 Subject: [PATCH] Report the running VeSync purifier fan level in preset modes (#183014) Co-authored-by: Joostlek --- homeassistant/components/vesync/fan.py | 26 +- tests/components/vesync/common.py | 3 + .../vesync/fixtures/vesync-devices.json | 15 + .../vesync/snapshots/test_binary_sensor.ambr | 108 ++++-- .../components/vesync/snapshots/test_fan.ambr | 315 ++++++++++++------ .../vesync/snapshots/test_humidifier.ambr | 108 ++++-- .../vesync/snapshots/test_light.ambr | 108 ++++-- .../vesync/snapshots/test_sensor.ambr | 261 +++++++++++++-- .../vesync/snapshots/test_switch.ambr | 132 ++++++++ .../vesync/snapshots/test_update.ambr | 292 ++++++++++------ tests/components/vesync/test_fan.py | 49 +++ 11 files changed, 1059 insertions(+), 358 deletions(-) diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 51a95d0fd818..6811c72329a7 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -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 diff --git a/tests/components/vesync/common.py b/tests/components/vesync/common.py index 017b2a203543..e98ff804bff1 100644 --- a/tests/components/vesync/common.py +++ b/tests/components/vesync/common.py @@ -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") ], diff --git a/tests/components/vesync/fixtures/vesync-devices.json b/tests/components/vesync/fixtures/vesync-devices.json index c5d5d91bbcd0..8a612f3e9764 100644 --- a/tests/components/vesync/fixtures/vesync-devices.json +++ b/tests/components/vesync/fixtures/vesync-devices.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" } ] } diff --git a/tests/components/vesync/snapshots/test_binary_sensor.ambr b/tests/components/vesync/snapshots/test_binary_sensor.ambr index 872d2b3536c1..08f12a069efd 100644 --- a/tests/components/vesync/snapshots/test_binary_sensor.ambr +++ b/tests/components/vesync/snapshots/test_binary_sensor.ambr @@ -143,6 +143,42 @@ list([ ]) # --- +# name: test_sensor_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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([ - ]) -# --- diff --git a/tests/components/vesync/snapshots/test_fan.ambr b/tests/components/vesync/snapshots/test_fan.ambr index 45410f8ad5bb..c8e147308242 100644 --- a/tests/components/vesync/snapshots/test_fan.ambr +++ b/tests/components/vesync/snapshots/test_fan.ambr @@ -403,6 +403,109 @@ 'state': 'on', }) # --- +# name: test_fan_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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({ + : list([ + 'auto', + 'pet', + 'sleep', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + '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': , + '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', + : 'Air Purifier Vital 200S', + 'mode': 'manual', + : 0, + : 25.0, + : None, + : list([ + 'auto', + 'pet', + 'sleep', + ]), + : , + }), + 'context': , + 'entity_id': 'fan.air_purifier_vital_200s', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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({ + : list([ + 'eco', + 'normal', + 'sleep', + 'turbo', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'fan', + 'entity_category': None, + 'entity_id': 'fan.corebreeze_432s', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + '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', + : 'CoreBreeze 432S', + 'mode': , + : True, + : 8, + : 8.333333333333334, + : 'normal', + : list([ + 'eco', + 'normal', + 'sleep', + 'turbo', + ]), + : , + }), + 'context': , + 'entity_id': 'fan.corebreeze_432s', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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({ - : list([ - 'eco', - 'normal', - 'sleep', - 'turbo', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'fan', - 'entity_category': None, - 'entity_id': 'fan.corebreeze_432s', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - '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': , - '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', - : 'CoreBreeze 432S', - 'mode': , - : True, - : 8, - : 8.333333333333334, - : 'normal', - : list([ - 'eco', - 'normal', - 'sleep', - 'turbo', - ]), - : , - }), - 'context': , - 'entity_id': 'fan.corebreeze_432s', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'off', - }) -# --- diff --git a/tests/components/vesync/snapshots/test_humidifier.ambr b/tests/components/vesync/snapshots/test_humidifier.ambr index c258a9e8538d..0e74862838bf 100644 --- a/tests/components/vesync/snapshots/test_humidifier.ambr +++ b/tests/components/vesync/snapshots/test_humidifier.ambr @@ -143,6 +143,42 @@ list([ ]) # --- +# name: test_humidifier_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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([ - ]) -# --- diff --git a/tests/components/vesync/snapshots/test_light.ambr b/tests/components/vesync/snapshots/test_light.ambr index bc15410d43ce..a1b9145ee4a7 100644 --- a/tests/components/vesync/snapshots/test_light.ambr +++ b/tests/components/vesync/snapshots/test_light.ambr @@ -143,6 +143,42 @@ list([ ]) # --- +# name: test_light_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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([ - ]) -# --- diff --git a/tests/components/vesync/snapshots/test_sensor.ambr b/tests/components/vesync/snapshots/test_sensor.ambr index b7a4971431b9..10c9a7291b3f 100644 --- a/tests/components/vesync/snapshots/test_sensor.ambr +++ b/tests/components/vesync/snapshots/test_sensor.ambr @@ -601,6 +601,195 @@ 'state': '5', }) # --- +# name: test_sensor_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + '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({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'PM2.5', + 'options': dict({ + }), + 'original_device_class': , + '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': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.air_purifier_vital_200s_filter_lifetime', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + }), + ]) +# --- +# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_air_quality] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Air Purifier Vital 200S Air quality', + }), + 'context': , + 'entity_id': 'sensor.air_purifier_vital_200s_air_quality', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'excellent', + }) +# --- +# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_filter_lifetime] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Air Purifier Vital 200S Filter lifetime', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.air_purifier_vital_200s_filter_lifetime', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '99', + }) +# --- +# name: test_sensor_state[Air Purifier Vital 200S][sensor.air_purifier_vital_200s_pm2_5] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'pm25', + : 'Air Purifier Vital 200S PM2.5', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.air_purifier_vital_200s_pm2_5', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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([ - ]) -# --- diff --git a/tests/components/vesync/snapshots/test_switch.ambr b/tests/components/vesync/snapshots/test_switch.ambr index a9fcfdda03ea..1939b38ec788 100644 --- a/tests/components/vesync/snapshots/test_switch.ambr +++ b/tests/components/vesync/snapshots/test_switch.ambr @@ -479,6 +479,138 @@ 'state': 'on', }) # --- +# name: test_switch_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.air_purifier_vital_200s_display', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.air_purifier_vital_200s_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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({ + : 'Air Purifier Vital 200S Child lock', + }), + 'context': , + 'entity_id': 'switch.air_purifier_vital_200s_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch_state[Air Purifier Vital 200S][switch.air_purifier_vital_200s_display] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Air Purifier Vital 200S Display', + }), + 'context': , + 'entity_id': 'switch.air_purifier_vital_200s_display', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_switch_state[CS158-AF Air Fryer Cooking][devices] list([ DeviceRegistryEntrySnapshot({ diff --git a/tests/components/vesync/snapshots/test_update.ambr b/tests/components/vesync/snapshots/test_update.ambr index 04d7ff10d744..83cf58f18999 100644 --- a/tests/components/vesync/snapshots/test_update.ambr +++ b/tests/components/vesync/snapshots/test_update.ambr @@ -387,6 +387,103 @@ 'state': 'on', }) # --- +# name: test_update_state[Air Purifier Vital 200S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entry_id': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'update', + 'entity_category': , + 'entity_id': 'update.air_purifier_vital_200s_firmware', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Firmware', + 'options': dict({ + }), + 'original_device_class': , + '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({ + : False, + : 'firmware', + : 0, + : '/api/brands/integration/vesync/icon.png', + : 'Air Purifier Vital 200S Firmware', + : False, + : None, + : None, + : None, + : None, + : None, + : , + : None, + : None, + }), + 'context': , + 'entity_id': 'update.air_purifier_vital_200s_firmware', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , + 'config_subentry_id': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'update', + 'entity_category': , + 'entity_id': 'update.corebreeze_432s_firmware', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Firmware', + 'options': dict({ + }), + 'original_device_class': , + '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({ + : False, + : 'firmware', + : 0, + : '/api/brands/integration/vesync/icon.png', + : 'CoreBreeze 432S Firmware', + : False, + : None, + : None, + : None, + : None, + : None, + : , + : None, + : None, + }), + 'context': , + 'entity_id': 'update.corebreeze_432s_firmware', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , - 'config_subentry_id': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - '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': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'update', - 'entity_category': , - 'entity_id': 'update.corebreeze_432s_firmware', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Firmware', - 'options': dict({ - }), - 'original_device_class': , - '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({ - : False, - : 'firmware', - : 0, - : '/api/brands/integration/vesync/icon.png', - : 'CoreBreeze 432S Firmware', - : False, - : None, - : None, - : None, - : None, - : None, - : , - : None, - : None, - }), - 'context': , - 'entity_id': 'update.corebreeze_432s_firmware', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'unknown', - }) -# --- diff --git a/tests/components/vesync/test_fan.py b/tests/components/vesync/test_fan.py index f7f8785ba07f..35d59183f692 100644 --- a/tests/components/vesync/test_fan.py +++ b/tests/components/vesync/test_fan.py @@ -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