diff --git a/README.md b/README.md index 5ceff41b..2c4ba960 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ pip install pyvesync 7. Vital 100S / 200S 8. Everest Air 9. Sprout Air Purifier +10. Eleva 300X ### Etekcity / Valceno Bulbs diff --git a/src/pyvesync/base_devices/purifier_base.py b/src/pyvesync/base_devices/purifier_base.py index 2e8e5d98..d4fa1c6b 100644 --- a/src/pyvesync/base_devices/purifier_base.py +++ b/src/pyvesync/base_devices/purifier_base.py @@ -40,6 +40,7 @@ class PurifierState(DeviceState): fan_level (int): Current fan level of the purifier. fan_set_level (int): Set fan level of the purifier. filter_life (int): Filter life percentage of the purifier. + carbon_filter_life (int): Carbon filter life percentage of the purifier. auto_preference_type (str): Auto preference type of the purifier. auto_room_size (int): Auto room size of the purifier. air_quality_level (AirQualityLevel): Air quality level of the purifier. @@ -71,6 +72,7 @@ class PurifierState(DeviceState): 'aq_percent', 'auto_preference_type', 'auto_room_size', + 'carbon_filter_life', 'child_lock', 'co2', 'display_forever', @@ -106,6 +108,7 @@ def __init__( self.fan_level: int | None = None self.fan_set_level: int | None = None self.filter_life: int | None = None + self.carbon_filter_life: int | None = None self.auto_preference_type: str | None = None self.auto_room_size: int | None = None self._air_quality_level: AirQualityLevel | None = None diff --git a/src/pyvesync/device_map.py b/src/pyvesync/device_map.py index 172b04c3..8846f62f 100644 --- a/src/pyvesync/device_map.py +++ b/src/pyvesync/device_map.py @@ -1026,6 +1026,26 @@ class ThermostatMap(DeviceMapTemplate): model_name='Sprout Air', setup_entry='LAP-B851S-WUS', ), + PurifierMap( + class_name='VeSyncAirBaseV2', + dev_types=['LAP-C341S-WEU'], + modes=[ + PurifierModes.SLEEP, + PurifierModes.MANUAL, + PurifierModes.AUTO, + ], + features=[PurifierFeatures.AIR_QUALITY], + fan_levels=list(range(1, 5)), + device_alias='Eleva 300X', + auto_preferences=[ + PurifierAutoPreference.DEFAULT, + PurifierAutoPreference.EFFICIENT, + PurifierAutoPreference.QUIET, + ], + model_display='LAP-C341S Series', + model_name='Eleva 300X', + setup_entry='LAP-C341S-WEU', + ), ] """List of ['PurifierMap'][pyvesync.device_map.PurifierMap] configuration objects for purifier devices.""" diff --git a/src/pyvesync/devices/vesyncpurifier.py b/src/pyvesync/devices/vesyncpurifier.py index ba3ba808..c756b9c4 100644 --- a/src/pyvesync/devices/vesyncpurifier.py +++ b/src/pyvesync/devices/vesyncpurifier.py @@ -460,6 +460,7 @@ def _set_state(self, details: InnerPurifierBaseResult) -> None: self.state.device_status = DeviceStatus.from_int(details.powerSwitch) self.state.mode = details.workMode self.state.filter_life = details.filterLifePercent + self.state.carbon_filter_life = details.carbonFilterLifePercent if details.fanSpeedLevel == 255: # noqa: PLR2004 self.state.fan_level = 0 else: diff --git a/src/pyvesync/models/purifier_models.py b/src/pyvesync/models/purifier_models.py index d510ef33..528ed5c1 100644 --- a/src/pyvesync/models/purifier_models.py +++ b/src/pyvesync/models/purifier_models.py @@ -41,27 +41,28 @@ class PurifierVitalDetailsResult(InnerPurifierBaseResult): """Vital 100S/200S and Everest Purifier Result Model.""" powerSwitch: int - filterLifePercent: int workMode: str manualSpeedLevel: int fanSpeedLevel: int AQLevel: int PM25: int - screenState: int - childLockSwitch: int - screenSwitch: int - lightDetectionSwitch: int - environmentLightState: int - scheduleCount: int - timerRemain: int - efficientModeTimeRemain: int - errorCode: int + filterLifePercent: int = 100 + screenState: int = 0 + childLockSwitch: int = 0 + screenSwitch: int = 0 + lightDetectionSwitch: int = 0 + environmentLightState: int = 0 + scheduleCount: int = 0 + timerRemain: int = 0 + efficientModeTimeRemain: int = 0 + errorCode: int = 0 autoPreference: V2AutoPreferences | None = None fanRotateAngle: int | None = None filterOpenState: int | None = None PM1: int | None = None PM10: int | None = None AQPercent: int | None = None + carbonFilterLifePercent: int | None = None @dataclass @@ -69,7 +70,7 @@ class V2AutoPreferences: """Vital 100S/200S Auto Preferences.""" autoPreferenceType: str - roomSize: int + roomSize: int | None = None @dataclass diff --git a/src/tests/api/vesyncpurifier/LAP-C341S-WEU.yaml b/src/tests/api/vesyncpurifier/LAP-C341S-WEU.yaml new file mode 100644 index 00000000..57c21f41 --- /dev/null +++ b/src/tests/api/vesyncpurifier/LAP-C341S-WEU.yaml @@ -0,0 +1,167 @@ +set_fan_speed: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: + levelIdx: 0 + levelType: wind + manualSpeedLevel: 3 + method: setLevel + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 +set_manual_mode: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: + levelIdx: 0 + levelType: wind + manualSpeedLevel: 1 + method: setLevel + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 +set_sleep_mode: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: + workMode: sleep + method: setPurifierMode + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 +turn_off: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: + powerSwitch: 0 + switchIdx: 0 + method: setSwitch + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 +turn_on: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: + powerSwitch: 1 + switchIdx: 0 + method: setSwitch + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 +update: + headers: + Content-Type: application/json; charset=UTF-8 + User-Agent: okhttp/3.12.1 + json_object: + acceptLanguage: en + accountID: sample_id + appVersion: 5.6.60 + cid: LAP-C341S-WEU-CID + configModel: ConfigModule + configModule: ConfigModule + debugMode: false + deviceId: LAP-C341S-WEU-CID + method: bypassV2 + payload: + data: {} + method: getPurifierStatus + source: APP + phoneBrand: pyvesync + phoneOS: Android + timeZone: America/New_York + token: sample_tk + traceId: TRACE_ID + userCountryCode: US + method: post + url: /cloud/v2/deviceManaged/bypassV2 diff --git a/src/tests/call_json_purifiers.py b/src/tests/call_json_purifiers.py index 79b02dcd..3179fc33 100644 --- a/src/tests/call_json_purifiers.py +++ b/src/tests/call_json_purifiers.py @@ -197,6 +197,25 @@ class PurifierDefaults: "timerRemain": 0, "efficientModeTimeRemain": 0, "errorCode": 0, + }, + "LAP-C341S-WEU": { # Eleva 300X + "powerSwitch": int(DeviceStatus.ON), + "filterLifePercent": PurifierDefaults.filter_life, + "workMode": PurifierDefaults.purifier_mode, + "manualSpeedLevel": PurifierDefaults.fan_set_level, + "fanSpeedLevel": PurifierDefaults.fan_level, + "AQLevel": int(PurifierDefaults.air_quality_enum), + "PM25": PurifierDefaults.air_quality_value_pm25, + "screenState": int(PurifierDefaults.display), + "childLockSwitch": int(PurifierDefaults.child_lock), + "screenSwitch": int(PurifierDefaults.display_config), + "lightDetectionSwitch": int(PurifierDefaults.light_detection), + "environmentLightState": int(PurifierDefaults.light_detected), + "autoPreference": {"autoPreferenceType": "default"}, + "scheduleCount": 0, + "timerRemain": 0, + "efficientModeTimeRemain": 0, + "errorCode": 0, }, "LAP-V201S": { # Vital 200S "powerSwitch": int(DeviceStatus.ON), @@ -318,6 +337,7 @@ class PurifierDefaults: "LAP-V201S": build_bypass_v2_response(inner_result=PURIFIER_DETAILS["LAP-V201S"]), "EL551S": build_bypass_v2_response(inner_result=PURIFIER_DETAILS["EL551S"]), "LAP-B851S-WUS": build_bypass_v2_response(inner_result=PURIFIER_DETAILS["LAP-B851S-WUS"]), + "LAP-C341S-WEU": build_bypass_v2_response(inner_result=PURIFIER_DETAILS["LAP-C341S-WEU"]), } FunctionResponses.default_factory = lambda: ( @@ -341,6 +361,7 @@ class PurifierDefaults: "LAP-V201S": deepcopy(FunctionResponsesV2), "EL551S": deepcopy(FunctionResponsesV2), "LAP-B851S-WUS": deepcopy(FunctionResponsesV2), + "LAP-C341S-WEU": deepcopy(FunctionResponsesV2), } # Add responses for methods with different responses than the default