From 70b9dfbca6c4df4ba9658f69e2be11d4852f64d2 Mon Sep 17 00:00:00 2001 From: Domocn <56492835+Domocn@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:40:37 +0100 Subject: [PATCH 1/5] feat: add Plasma support for Core 400S + nightlight fixes for humidifiers - Add PLASMA to PurifierFeatures enum (const.py) - Add toggle_plasma/turn_on_plasma/turn_off_plasma methods to VeSyncAirBypass - Add plasma field handling in _update_details - Add PurifierFeatures.PLASMA to Core400S device map - Add HumidifierFeatures.NIGHTLIGHT to Classic200S device map - Add HumidifierFeatures.NIGHTLIGHT_RGB to Classic300S device map Fixes #521, #160387, #502 --- src/pyvesync/const.py | 3 +++ src/pyvesync/device_map.py | 5 ++-- src/pyvesync/devices/vesyncpurifier.py | 35 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/pyvesync/const.py b/src/pyvesync/const.py index a0571b4..7e56218 100644 --- a/src/pyvesync/const.py +++ b/src/pyvesync/const.py @@ -506,6 +506,7 @@ class HumidifierFeatures(Features): WARM_MIST = 'warm_mist' AUTO_STOP = 'auto_stop' NIGHTLIGHT_BRIGHTNESS = 'nightlight_brightness' + NIGHTLIGHT_RGB = 'nightlight_rgb' DRYING_MODE = 'drying_mode' @@ -518,6 +519,7 @@ class PurifierFeatures(Features): AIR_QUALITY: Air quality status. VENT_ANGLE: Vent angle status. LIGHT_DETECT: Light detection status. + PLASMA: Plasma/PlasmaPro mode status. PM25: PM2.5 level status. PM10: PM10 level status. PM1: PM1 level status. @@ -531,6 +533,7 @@ class PurifierFeatures(Features): AIR_QUALITY = 'air_quality' VENT_ANGLE = 'fan_rotate' LIGHT_DETECT = 'light_detect' + PLASMA = 'plasma' PM25 = 'pm25' PM10 = 'pm10' PM1 = 'pm1' diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index d467eb1..6631c3b 100644 --- a/src/pyvesync/device_map.py +++ b/src/pyvesync/device_map.py @@ -593,6 +593,7 @@ class ThermostatMap(DeviceMapTemplate): features=[ HumidifierFeatures.NIGHTLIGHT, HumidifierFeatures.NIGHTLIGHT_BRIGHTNESS, + HumidifierFeatures.NIGHTLIGHT_RGB, HumidifierFeatures.AUTO_STOP, ], mist_modes={ @@ -609,7 +610,7 @@ class ThermostatMap(DeviceMapTemplate): HumidifierMap( class_name='VeSyncHumid200S', dev_types=['Classic200S'], - features=[HumidifierFeatures.AUTO_STOP], + features=[HumidifierFeatures.AUTO_STOP, HumidifierFeatures.NIGHTLIGHT], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.MANUAL: 'manual', @@ -833,7 +834,7 @@ class ThermostatMap(DeviceMapTemplate): class_name='VeSyncAirBypass', dev_types=['Core400S', 'LAP-C401S-WJP', 'LAP-C401S-WUSR', 'LAP-C401S-WAAA'], modes=[PurifierModes.SLEEP, PurifierModes.MANUAL, PurifierModes.AUTO], - features=[PurifierFeatures.AIR_QUALITY], + features=[PurifierFeatures.AIR_QUALITY, PurifierFeatures.PLASMA], fan_levels=list(range(1, 5)), device_alias='Core 400S', auto_preferences=[ diff --git a/src/pyvesync/devices/vesyncpurifier.py b/src/pyvesync/devices/vesyncpurifier.py index ba3ba80..e1c48f7 100644 --- a/src/pyvesync/devices/vesyncpurifier.py +++ b/src/pyvesync/devices/vesyncpurifier.py @@ -134,6 +134,8 @@ def _set_purifier_state(self, result: PurifierCoreDetailsResult) -> None: DeviceStatus.ON if result.display else DeviceStatus.OFF ) self.state.child_lock = result.child_lock or False + if hasattr(result, 'plasma'): + self.state.plasma = result.plasma config = result.configuration if config is not None: self.state.display_set_status = ( @@ -284,6 +286,39 @@ async def toggle_child_lock(self, toggle: bool | None = None) -> bool: self.state.connection_status = ConnectionStatus.ONLINE return True + async def toggle_plasma(self, toggle: bool | None = None) -> bool: + """Toggle plasma/PlasmaPro mode on supported purifiers. + + Set plasma to on or off. + + Args: + toggle (bool): True to turn plasma on, False to turn off. + If None, toggles current state. + + Returns: + bool: True if plasma was set successfully, False otherwise. + """ + if toggle is None: + toggle = not getattr(self.state, 'plasma', False) + data = {'plasma': toggle} + + r_dict = await self.call_bypassv2_api('setPlasma', data) + r = Helpers.process_dev_response(_LOGGER, 'toggle_plasma', self, r_dict) + if r is None: + return False + + self.state.plasma = toggle + self.state.connection_status = ConnectionStatus.ONLINE + return True + + async def turn_on_plasma(self) -> bool: + """Turn plasma/PlasmaPro mode on.""" + return await self.toggle_plasma(True) + + async def turn_off_plasma(self) -> bool: + """Turn plasma/PlasmaPro mode off.""" + return await self.toggle_plasma(False) + async def reset_filter(self) -> bool: """Reset filter to 100%. From 9565911541e3e0b87c6057b44d1b8709c85c85c6 Mon Sep 17 00:00:00 2001 From: Domocn <56492835+Domocn@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:43:43 +0100 Subject: [PATCH 2/5] feat: add Plasma for Core 400S, nightlight fixes for humidifiers - Add PLASMA to PurifierFeatures enum (const.py) - Add toggle_plasma/turn_on_plasma/turn_off_plasma methods to VeSyncAirBypass - Add plasma field handling in _update_details - Add PurifierFeatures.PLASMA to Core400S device map - Add HumidifierFeatures.NIGHTLIGHT_RGB to Classic300S device map - Add HumidifierFeatures.NIGHTLIGHT to Classic200S device map - Add HumidifierFeatures.NIGHTLIGHT to OasisMist 450S (EU and US) device maps - Add toggle_nightlight/set_nightlight_brightness overrides to VeSyncSproutHumid to wire up the existing _set_nightlight_state helper to the public API Fixes #521, #528, #500, #160387, #502 --- src/pyvesync/device_map.py | 4 ++-- src/pyvesync/devices/vesynchumidifier.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index 6631c3b..98253b3 100644 --- a/src/pyvesync/device_map.py +++ b/src/pyvesync/device_map.py @@ -685,7 +685,7 @@ class ThermostatMap(DeviceMapTemplate): HumidifierMap( class_name='VeSyncHumid200300S', dev_types=['LUH-O451S-WEU'], - features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP], + features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP, HumidifierFeatures.NIGHTLIGHT], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.SLEEP: 'sleep', @@ -701,7 +701,7 @@ class ThermostatMap(DeviceMapTemplate): HumidifierMap( class_name='VeSyncHumid200300S', dev_types=['LUH-O451S-WUS', 'LUH-O451S-WUSR', 'LUH-O601S-WUS', 'LUH-O601S-KUS'], - features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP], + features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP, HumidifierFeatures.NIGHTLIGHT], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.SLEEP: 'sleep', diff --git a/src/pyvesync/devices/vesynchumidifier.py b/src/pyvesync/devices/vesynchumidifier.py index 74df19d..5962afe 100644 --- a/src/pyvesync/devices/vesynchumidifier.py +++ b/src/pyvesync/devices/vesynchumidifier.py @@ -1116,6 +1116,22 @@ async def _set_nightlight_state( self.state.connection_status = ConnectionStatus.ONLINE return True + async def toggle_nightlight(self, toggle: bool | None = None) -> bool: + """Toggle nightlight on/off for Sprout Humidifier. + + Overrides base class to use setLightStatus API. + """ + if toggle is None: + toggle = self.state.nightlight_status != DeviceStatus.ON + return await self._set_nightlight_state(toggle) + + async def set_nightlight_brightness(self, brightness: int) -> bool: + """Set nightlight brightness for Sprout Humidifier. + + Overrides base class to use setLightStatus API. + """ + return await self._set_nightlight_state(True, brightness=brightness) + async def toggle_automatic_stop(self, toggle: bool | None = None) -> bool: if toggle is None: toggle = self.state.automatic_stop_config is not True From 02e1fec9ac3d74665d9ddf2ff7d60793fe31491a Mon Sep 17 00:00:00 2001 From: Domocn <56492835+Domocn@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:53:25 +0100 Subject: [PATCH 3/5] feat: add new device types from APK analysis - Add LPF-F361S-WUS, LPF-F362S-WUSR, LPF-F461S-WUS to VeSyncTowerFan - Add LPF-R382S-AEU, LPF-R382S-AUS to VeSyncPedestalFan - Add LUH-A603S-WUK (LV600S UK variant) to VeSyncLV600S - Add CS358-AF to VeSyncAirFryer158 Fixes #525, #523, #526 --- src/pyvesync/device_map.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index 98253b3..3e67b95 100644 --- a/src/pyvesync/device_map.py +++ b/src/pyvesync/device_map.py @@ -668,6 +668,7 @@ class ThermostatMap(DeviceMapTemplate): class_name='VeSyncLV600S', dev_types=[ 'LUH-A603S-WUS', + 'LUH-A603S-WUK', ], features=[HumidifierFeatures.WARM_MIST], mist_modes={ @@ -1010,7 +1011,8 @@ class ThermostatMap(DeviceMapTemplate): fan_modules: list[FanMap] = [ FanMap( class_name='VeSyncTowerFan', - dev_types=['LTF-F422S-KEU', 'LTF-F422S-WUSR', 'LTF-F422S-WJP', 'LTF-F422S-WUS'], + dev_types=['LTF-F422S-KEU', 'LTF-F422S-WUSR', 'LTF-F422S-WJP', 'LTF-F422S-WUS', + 'LPF-F361S-WUS', 'LPF-F362S-WUSR', 'LPF-F461S-WUS'], modes={ FanModes.NORMAL: 'normal', FanModes.TURBO: 'turbo', @@ -1038,7 +1040,7 @@ class ThermostatMap(DeviceMapTemplate): ), FanMap( class_name='VeSyncPedestalFan', - dev_types=['LPF-R432S-AEU', 'LPF-R432S-AUS'], + dev_types=['LPF-R432S-AEU', 'LPF-R432S-AUS', 'LPF-R382S-AEU', 'LPF-R382S-AUS'], modes={ FanModes.NORMAL: 'normal', FanModes.TURBO: 'turbo', @@ -1072,10 +1074,10 @@ class ThermostatMap(DeviceMapTemplate): AirFryerMap( class_name='VeSyncAirFryer158', module=vesynckitchen, - dev_types=['CS137-AF/CS158-AF', 'CS158-AF', 'CS137-AF'], + dev_types=['CS137-AF/CS158-AF', 'CS158-AF', 'CS137-AF', 'CS358-AF'], device_alias='Air Fryer', - model_display='CS158/159/168/169-AF Series', - model_name='Smart/Pro/Pro Gen 2 5.8 Qt. Air Fryer', + model_display='CS158/159/168/169/358-AF Series', + model_name='Smart/Pro/Pro Gen 2 5.8/6.8 Qt. Air Fryer', setup_entry='CS137-AF/CS158-AF', ) ] From eacadce57ac9873fa80f5f03d00d9613777a8e52 Mon Sep 17 00:00:00 2001 From: Domocn <56492835+Domocn@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:02:28 +0100 Subject: [PATCH 4/5] fix: log unknown device types at INFO level so users can report them Changes logger.debug to logger.info for unrecognized device types, making them visible in default Home Assistant logs without needing to enable debug logging. This helps users report new device types for inclusion in the device map. --- src/pyvesync/device_container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyvesync/device_container.py b/src/pyvesync/device_container.py index 6b4926a..5cf1a0f 100644 --- a/src/pyvesync/device_container.py +++ b/src/pyvesync/device_container.py @@ -156,7 +156,7 @@ def _build_device_instance( """ device_features = get_device_config(device.deviceType) if device_features is None: - logger.debug('Device type %s not found in device map', device.deviceType) + logger.info('Device type %s not found in device map - please report this', device.deviceType) return None dev_class = device_features.class_name dev_module = device_features.module From 6a876eb41c5fa118377394238228d629f315987b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:06:39 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pyvesync/device_container.py | 5 ++++- src/pyvesync/device_map.py | 23 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/pyvesync/device_container.py b/src/pyvesync/device_container.py index 5cf1a0f..098ae9f 100644 --- a/src/pyvesync/device_container.py +++ b/src/pyvesync/device_container.py @@ -156,7 +156,10 @@ def _build_device_instance( """ device_features = get_device_config(device.deviceType) if device_features is None: - logger.info('Device type %s not found in device map - please report this', device.deviceType) + logger.info( + 'Device type %s not found in device map - please report this', + device.deviceType, + ) return None dev_class = device_features.class_name dev_module = device_features.module diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index 3e67b95..ef415b5 100644 --- a/src/pyvesync/device_map.py +++ b/src/pyvesync/device_map.py @@ -686,7 +686,11 @@ class ThermostatMap(DeviceMapTemplate): HumidifierMap( class_name='VeSyncHumid200300S', dev_types=['LUH-O451S-WEU'], - features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP, HumidifierFeatures.NIGHTLIGHT], + features=[ + HumidifierFeatures.WARM_MIST, + HumidifierFeatures.AUTO_STOP, + HumidifierFeatures.NIGHTLIGHT, + ], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.SLEEP: 'sleep', @@ -702,7 +706,11 @@ class ThermostatMap(DeviceMapTemplate): HumidifierMap( class_name='VeSyncHumid200300S', dev_types=['LUH-O451S-WUS', 'LUH-O451S-WUSR', 'LUH-O601S-WUS', 'LUH-O601S-KUS'], - features=[HumidifierFeatures.WARM_MIST, HumidifierFeatures.AUTO_STOP, HumidifierFeatures.NIGHTLIGHT], + features=[ + HumidifierFeatures.WARM_MIST, + HumidifierFeatures.AUTO_STOP, + HumidifierFeatures.NIGHTLIGHT, + ], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.SLEEP: 'sleep', @@ -1011,8 +1019,15 @@ class ThermostatMap(DeviceMapTemplate): fan_modules: list[FanMap] = [ FanMap( class_name='VeSyncTowerFan', - dev_types=['LTF-F422S-KEU', 'LTF-F422S-WUSR', 'LTF-F422S-WJP', 'LTF-F422S-WUS', - 'LPF-F361S-WUS', 'LPF-F362S-WUSR', 'LPF-F461S-WUS'], + dev_types=[ + 'LTF-F422S-KEU', + 'LTF-F422S-WUSR', + 'LTF-F422S-WJP', + 'LTF-F422S-WUS', + 'LPF-F361S-WUS', + 'LPF-F362S-WUSR', + 'LPF-F461S-WUS', + ], modes={ FanModes.NORMAL: 'normal', FanModes.TURBO: 'turbo',