Skip to content

Commit 433b516

Browse files
authored
Merge pull request #198 from webdjoe/fix-purifier-models
Air Purifier Minor Bug Fixes Remove Vital 100S from 200S config dict entry Fix light detection API call Ensure enabled property is set correctly Fix Air Purifier models
2 parents 2d70bb4 + fac605a commit 433b516

File tree

4 files changed

+68
-42
lines changed

4 files changed

+68
-42
lines changed

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
include README.md LICENSE requirements.txt
2+
exclude test
3+
prune test

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name='pyvesync',
13-
version='2.1.8',
13+
version='2.1.9',
1414
description='pyvesync is a library to manage Etekcity\
1515
Devices, Cosori Air Fryers and Levoit Air \
1616
Purifiers run on the VeSync app.',
@@ -28,9 +28,8 @@
2828
'Programming Language :: Python :: 3.8',
2929
],
3030
keywords=['iot', 'vesync', 'levoit', 'etekcity', 'cosori', 'valceno'],
31-
packages=find_packages('src', exclude=['tests', 'tests.*']),
31+
packages=find_packages('src', exclude=['tests', 'test*']),
3232
package_dir={'': 'src'},
33-
zip_safe=False,
3433
install_requires=['requests>=2.20.0'],
3534
extras_require={
3635
'dev': ['pytest', 'pytest-cov', 'yaml', 'tox']

src/pyvesync/helpers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ def redactor(cls, stringvalue: str) -> str:
194194
'##_REDACTED_##', stringvalue)
195195
return stringvalue
196196

197+
@staticmethod
198+
def nested_code_check(response: dict) -> bool:
199+
"""Return true if all code values are 0."""
200+
if isinstance(response, dict):
201+
for key, value in response.items():
202+
if key == 'code':
203+
if value != 0:
204+
return False
205+
elif isinstance(value, dict):
206+
if not Helpers.nested_code_check(value):
207+
return False
208+
return True
209+
197210
@staticmethod
198211
def call_api(api: str, method: str, json_object: Optional[dict] = None,
199212
headers: Optional[dict] = None) -> tuple:

src/pyvesync/vesyncfan.py

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,14 @@
9999
'module': 'VeSyncVital',
100100
'models': ['LAP-V102S-AASR', 'LAP-V102S-WUS', 'LAP-V102S-WEU',
101101
'LAP-V102S-AUSR', 'LAP-V102S-WJP'],
102-
'modes': ['manual', 'auto', 'sleep', 'off'],
102+
'modes': ['manual', 'auto', 'sleep', 'off', 'pet'],
103103
'features': ['air_quality'],
104104
'levels': list(range(1, 5))
105105
},
106106
'Vital200S': {
107107
'module': 'VeSyncVital',
108108
'models': ['LAP-V201S-AASR', 'LAP-V201S-WJP', 'LAP-V201S-WEU',
109-
'LAP-V102S-AUSR', 'LAP-V201S-WUS'],
109+
'LAP-V201S-WUS', 'LAP-V201-AUSR'],
110110
'modes': ['manual', 'auto', 'sleep', 'off', 'pet'],
111111
'features': ['air_quality'],
112112
'levels': list(range(1, 5))
@@ -768,28 +768,20 @@ def get_details(self) -> None:
768768
headers=head,
769769
json_object=body,
770770
)
771-
if not isinstance(r, dict):
772-
logger.debug('Error in purifier response')
771+
if Helpers.nested_code_check(r) is False or not isinstance(r, dict):
772+
logger.debug('Error getting purifier details')
773+
self.connection_status = 'offline'
773774
return
774-
if not isinstance(r.get('result'), dict):
775-
logger.debug('Error in purifier response')
776-
return
777-
outer_result = r.get('result', {})
778-
inner_result = None
779775

780-
if outer_result:
781-
inner_result = r.get('result', {}).get('result')
782-
if inner_result is not None and Helpers.code_check(r):
783-
if outer_result.get('code') == 0:
784-
self.build_purifier_dict(inner_result)
785-
else:
786-
logger.debug('error in inner result dict from purifier')
787-
if inner_result.get('configuration', {}):
788-
self.build_config_dict(inner_result.get('configuration', {}))
789-
else:
790-
logger.debug('No configuration found in purifier status')
776+
inner_result = r.get('result', {}).get('result')
777+
778+
if inner_result is not None:
779+
self.build_purifier_dict(inner_result)
791780
else:
792-
logger.debug('Error in purifier response')
781+
self.connection_status = 'offline'
782+
logger.debug('error in inner result dict from purifier')
783+
if inner_result.get('configuration', {}):
784+
self.build_config_dict(inner_result.get('configuration', {}))
793785

794786
def build_api_dict(self, method: str) -> Tuple[Dict, Dict]:
795787
"""Return default body for Levoit Vital 100S/200S API."""
@@ -808,26 +800,23 @@ def build_api_dict(self, method: str) -> Tuple[Dict, Dict]:
808800

809801
def build_purifier_dict(self, dev_dict: dict) -> None:
810802
"""Build Bypass purifier status dictionary."""
811-
power_switch = dev_dict.get('power_switch', 0)
803+
self.connection_status = 'online'
804+
power_switch = bool(dev_dict.get('powerSwitch', 0))
812805
self.enabled = power_switch
813-
self.device_status = 'on' if power_switch else 'off'
806+
self.device_status = 'on' if power_switch is True else 'off'
814807
self.mode = dev_dict.get('workMode', 'manual')
815808

816809
self.speed = dev_dict.get('fanSpeedLevel', 0)
817810

818811
self.set_speed_level = dev_dict.get('manualSpeedLevel', 1)
819812

820813
self.details['filter_life'] = dev_dict.get('filterLifePercent', 0)
821-
self.details['display'] = dev_dict.get('display', False)
822814
self.details['child_lock'] = bool(dev_dict.get('childLockSwitch', 0))
823-
self.details['night_light'] = dev_dict.get('night_light', 'off')
824815
self.details['display'] = bool(dev_dict.get('screenState', 0))
825-
self.details['display_forever'] = dev_dict.get('display_forever', False)
826816
self.details['light_detection_switch'] = bool(
827817
dev_dict.get('lightDetectionSwitch', 0))
828818
self.details['environment_light_state'] = bool(
829819
dev_dict.get('environmentLightState', 0))
830-
self.details['screen_state'] = bool(dev_dict.get('screenState', 0))
831820
self.details['screen_switch'] = bool(dev_dict.get('screenSwitch', 0))
832821

833822
if self.air_quality_feature is True:
@@ -848,15 +837,12 @@ def pet_mode(self) -> bool:
848837

849838
def set_light_detection(self, toggle: bool) -> bool:
850839
"""Enable/Disable Light Detection Feature."""
851-
if toggle is True:
852-
toggle_id = 1
853-
else:
854-
toggle_id = 0
840+
toggle_id = int(toggle)
855841
if self.details['light_detection_switch'] == toggle_id:
856842
logger.debug("Light Detection is already set to %s", toggle_id)
857843
return True
858844

859-
head, body = self.build_api_dict('setLightDetectionSwitch')
845+
head, body = self.build_api_dict('setLightDetection')
860846
body['payload']['data']['lightDetectionSwitch'] = toggle_id
861847
r, _ = Helpers.call_api(
862848
'/cloud/v2/deviceManaged/bypassV2',
@@ -865,7 +851,7 @@ def set_light_detection(self, toggle: bool) -> bool:
865851
json_object=body,
866852
)
867853

868-
if r is not None and Helpers.code_check(r):
854+
if r is not None and Helpers.nested_code_check(r):
869855
self.details['light_detection'] = toggle
870856
return True
871857
logger.debug("Error toggling purifier - %s",
@@ -903,7 +889,7 @@ def toggle_switch(self, toggle: bool) -> bool:
903889
json_object=body,
904890
)
905891

906-
if r is not None and Helpers.code_check(r):
892+
if r is not None and Helpers.nested_code_check(r):
907893
if toggle:
908894
self.device_status = 'on'
909895
else:
@@ -914,7 +900,7 @@ def toggle_switch(self, toggle: bool) -> bool:
914900
return False
915901

916902
def set_child_lock(self, mode: bool) -> bool:
917-
"""Levoit 100S set Child Lock."""
903+
"""Levoit 100S/200S set Child Lock."""
918904
if mode:
919905
toggle_id = 1
920906
else:
@@ -931,7 +917,7 @@ def set_child_lock(self, mode: bool) -> bool:
931917
json_object=body,
932918
)
933919

934-
if r is not None and Helpers.code_check(r):
920+
if r is not None and Helpers.nested_code_check(r):
935921
self.details['child_lock'] = mode
936922
return True
937923

@@ -956,7 +942,7 @@ def set_display(self, mode: bool) -> bool:
956942
json_object=body,
957943
)
958944

959-
if r is not None and Helpers.code_check(r):
945+
if r is not None and Helpers.nested_code_check(r):
960946
self.details['screen_switch'] = mode
961947
return True
962948

@@ -1006,7 +992,7 @@ def set_timer(self, timer_duration: int, action: str = 'off',
1006992
json_object=body,
1007993
)
1008994

1009-
if r is not None and Helpers.code_check(r):
995+
if r is not None and Helpers.nested_code_check(r):
1010996
self.timer = Timer(timer_duration, action)
1011997
return True
1012998

@@ -1026,7 +1012,7 @@ def clear_timer(self) -> bool:
10261012
json_object=body,
10271013
)
10281014

1029-
if r is not None and Helpers.code_check(r):
1015+
if r is not None and Helpers.nested_code_check(r):
10301016
self.timer = None
10311017
return True
10321018

@@ -1166,6 +1152,32 @@ def mode_toggle(self, mode: str) -> bool:
11661152
logger.debug('Error setting purifier mode')
11671153
return False
11681154

1155+
def displayJSON(self) -> str:
1156+
"""Return air purifier status and properties in JSON output."""
1157+
sup = super().displayJSON()
1158+
sup_val = json.loads(sup)
1159+
sup_val.update(
1160+
{
1161+
'Mode': self.mode,
1162+
'Filter Life': str(self.details['filter_life']),
1163+
'Fan Level': str(self.speed),
1164+
'Display On': self.details['display'],
1165+
'Child Lock': self.details['child_lock'],
1166+
'Night Light': str(self.details['night_light']),
1167+
'Display Set On': self.details['screen_switch'],
1168+
'Light Detection Enabled': self.details['light_detection_switch'],
1169+
'Environment Light State': self.details['environment_light_state']
1170+
}
1171+
)
1172+
if self.air_quality_feature is True:
1173+
sup_val.update(
1174+
{'Air Quality Level': str(self.details.get('air_quality', ''))}
1175+
)
1176+
sup_val.update(
1177+
{'Air Quality Value': str(self.details.get('air_quality_value', ''))}
1178+
)
1179+
return json.dumps(sup_val, indent=4)
1180+
11691181

11701182
class VeSyncAir131(VeSyncBaseDevice):
11711183
"""Levoit Air Purifier Class."""

0 commit comments

Comments
 (0)