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_container.py b/src/pyvesync/device_container.py index 6b4926a..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.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 diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index d467eb1..ef415b5 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', @@ -667,6 +668,7 @@ class ThermostatMap(DeviceMapTemplate): class_name='VeSyncLV600S', dev_types=[ 'LUH-A603S-WUS', + 'LUH-A603S-WUK', ], features=[HumidifierFeatures.WARM_MIST], mist_modes={ @@ -684,7 +686,11 @@ 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', @@ -700,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], + features=[ + HumidifierFeatures.WARM_MIST, + HumidifierFeatures.AUTO_STOP, + HumidifierFeatures.NIGHTLIGHT, + ], mist_modes={ HumidifierModes.AUTO: 'auto', HumidifierModes.SLEEP: 'sleep', @@ -833,7 +843,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=[ @@ -1009,7 +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'], + 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', @@ -1037,7 +1055,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', @@ -1071,10 +1089,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', ) ] 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 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%.