Skip to content

Commit f0ccddb

Browse files
authored
Merge pull request #203 from webdjoe/OASISMIST1000S
Add Levoit Oasismist 1000S Humidifier
2 parents 433b516 + d149d7c commit f0ccddb

File tree

5 files changed

+575
-2
lines changed

5 files changed

+575
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pip install pyvesync
111111
3. LUH-D301S-WEU Dual (200S)
112112
4. LV600S
113113
5. OasisMist LUS-O415S-WUS
114+
6. OasisMist LUH-M101S-WUS
114115

115116
Cosori Air Fryer
116117

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name='pyvesync',
13-
version='2.1.9',
13+
version='2.1.10',
1414
description='pyvesync is a library to manage Etekcity\
1515
Devices, Cosori Air Fryers and Levoit Air \
1616
Purifiers run on the VeSync app.',

src/pyvesync/vesyncfan.py

Lines changed: 267 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@
5151
'mist_modes': ['humidity', 'sleep', 'manual'],
5252
'mist_levels': list(range(1, 10)),
5353
'warm_mist_levels': list(range(4))
54-
},
54+
},
55+
'OASISMIST1000S': {
56+
'module': 'VeSyncHumid1000S',
57+
'models': ['LUH-M101S-WUS'],
58+
'features': [],
59+
'mist_modes': ['auto', 'sleep', 'manual'],
60+
'mist_levels': list(range(1, 10))
61+
}
5562
}
5663

5764

@@ -2001,3 +2008,262 @@ def set_display(self, mode: bool) -> bool:
20012008
return True
20022009
logger.debug("Error toggling 200S display - %s", self.device_name)
20032010
return False
2011+
2012+
2013+
class VeSyncHumid1000S(VeSyncHumid200300S):
2014+
"""Levoit OasisMist 1000S Specific class."""
2015+
2016+
def __init__(self, details, manager):
2017+
"""Initialize levoit 1000S device class."""
2018+
super().__init__(details, manager)
2019+
self.connection_status: str = details.get('deviceProp', {}).get(
2020+
'connectionStatus', None)
2021+
2022+
self._api_modes = ['getHumidifierStatus', 'setAutoStopSwitch',
2023+
'setSwitch', 'setVirtualLevel', 'setTargetHumidity',
2024+
'setHumidityMode', 'setDisplay']
2025+
2026+
def build_humid_dict(self, dev_dict: Dict[str, str]) -> None:
2027+
"""Build humidifier status dictionary."""
2028+
super().build_humid_dict(dev_dict)
2029+
self.device_status = 'off' if dev_dict.get('powerSwitch', 0) == 0 else 'on'
2030+
self.details['mist_virtual_level'] = dev_dict.get(
2031+
'virtualLevel', 0)
2032+
self.details['mist_level'] = dev_dict.get('mistLevel', 0)
2033+
self.details['mode'] = dev_dict.get('workMode', 'manual')
2034+
self.details['water_lacks'] = bool(dev_dict.get('waterLacksState', 0))
2035+
self.details['humidity_high'] = bool(int(dev_dict.get('targetHumidity', 0)) <
2036+
int(dev_dict.get('humidity', 0)))
2037+
self.details['water_tank_lifted'] = bool(dev_dict.get(
2038+
'waterTankLifted', 0))
2039+
self.details['automatic_stop_reach_target'] = bool(dev_dict.get(
2040+
'autoStopState', 1
2041+
))
2042+
self.details['display'] = bool(dev_dict['screenState'])
2043+
2044+
def build_config_dict(self, conf_dict):
2045+
"""Build configuration dict for humidifier."""
2046+
self.config['auto_target_humidity'] = conf_dict.get(
2047+
'targetHumidity', 0)
2048+
self.config['display'] = bool(conf_dict.get('screenSwitch', 0))
2049+
self.config['automatic_stop'] = bool(conf_dict.get('autoStopSwitch', 1))
2050+
2051+
def get_details(self) -> None:
2052+
"""Build Humidifier details dictionary."""
2053+
head = Helpers.bypass_header()
2054+
body = Helpers.bypass_body_v2(self.manager)
2055+
body['cid'] = self.cid
2056+
body['configModule'] = self.config_module
2057+
body['payload'] = {
2058+
'method': 'getHumidifierStatus',
2059+
'source': 'APP',
2060+
'data': {}
2061+
}
2062+
2063+
r, _ = Helpers.call_api(
2064+
'/cloud/v2/deviceManaged/bypassV2',
2065+
method='post',
2066+
headers=head,
2067+
json_object=body,
2068+
)
2069+
if r is None or not isinstance(r, dict):
2070+
logger.debug("Error getting status of %s ", self.device_name)
2071+
return
2072+
outer_result = r.get('result', {})
2073+
inner_result = None
2074+
2075+
if outer_result is not None:
2076+
inner_result = r.get('result', {}).get('result')
2077+
if inner_result is not None and Helpers.code_check(r):
2078+
if outer_result.get('code') == 0:
2079+
self.connection_status = 'online'
2080+
self.build_humid_dict(inner_result)
2081+
self.build_config_dict(inner_result)
2082+
else:
2083+
logger.debug('error in inner result dict from humidifier')
2084+
elif r.get('code') == -11300030:
2085+
logger.debug('%s device offline', self.device_name)
2086+
self.connection_status = 'offline'
2087+
self.device_status = 'off'
2088+
else:
2089+
logger.debug('Error in humidifier response')
2090+
2091+
def set_display(self, mode: bool) -> bool:
2092+
"""Toggle display on/off."""
2093+
if not isinstance(mode, bool):
2094+
logger.debug("Mode must be True or False")
2095+
return False
2096+
2097+
head, body = self.build_api_dict('setDisplay')
2098+
2099+
body['payload']['data'] = {
2100+
'screenSwitch': int(mode)
2101+
}
2102+
2103+
r, _ = Helpers.call_api(
2104+
'/cloud/v2/deviceManaged/bypassV2',
2105+
method='post',
2106+
headers=head,
2107+
json_object=body,
2108+
)
2109+
2110+
if r is not None and Helpers.code_check(r):
2111+
return True
2112+
logger.debug("Error toggling purifier display - %s",
2113+
self.device_name)
2114+
return False
2115+
2116+
def set_humidity_mode(self, mode: str) -> bool:
2117+
"""Set humidifier mode - sleep, auto or manual."""
2118+
if mode.lower() not in self.mist_modes:
2119+
logger.debug('Invalid humidity mode used - %s',
2120+
mode)
2121+
logger.debug('Proper modes for this device are - %s',
2122+
str(self.mist_modes))
2123+
return False
2124+
head, body = self.build_api_dict('setHumidityMode')
2125+
if not head and not body:
2126+
return False
2127+
body['payload']['data'] = {
2128+
'workMode': mode.lower()
2129+
}
2130+
2131+
r, _ = Helpers.call_api(
2132+
'/cloud/v2/deviceManaged/bypassV2',
2133+
method='post',
2134+
headers=head,
2135+
json_object=body,
2136+
)
2137+
2138+
if r is not None and Helpers.code_check(r):
2139+
return True
2140+
logger.debug('Error setting humidity mode')
2141+
return False
2142+
2143+
def set_sleep_mode(self):
2144+
"""Set humifier to manual mode with 1 mist level."""
2145+
return self.set_humidity_mode('sleep')
2146+
2147+
def set_mist_level(self, level) -> bool:
2148+
"""Set humidifier mist level with int."""
2149+
try:
2150+
level = int(level)
2151+
except ValueError:
2152+
level = str(level)
2153+
if level not in self.mist_levels:
2154+
logger.debug('Humidifier mist level out of range')
2155+
return False
2156+
2157+
head, body = self.build_api_dict('setVirtualLevel')
2158+
if not head and not body:
2159+
return False
2160+
2161+
body['payload']['data'] = {
2162+
'levelIdx': 0,
2163+
'virtualLevel': level,
2164+
'levelType': 'mist'
2165+
}
2166+
2167+
r, _ = Helpers.call_api(
2168+
'/cloud/v2/deviceManaged/bypassV2',
2169+
method='post',
2170+
headers=head,
2171+
json_object=body,
2172+
)
2173+
2174+
if r is not None and Helpers.code_check(r):
2175+
return True
2176+
logger.debug('Error setting mist level')
2177+
return False
2178+
2179+
def toggle_switch(self, toggle: bool) -> bool:
2180+
"""Toggle humidifier on/off."""
2181+
if not isinstance(toggle, bool):
2182+
logger.debug('Invalid toggle value for humidifier switch')
2183+
return False
2184+
2185+
head = Helpers.bypass_header()
2186+
body = Helpers.bypass_body_v2(self.manager)
2187+
body['cid'] = self.cid
2188+
body['configModule'] = self.config_module
2189+
body['payload'] = {
2190+
'data': {
2191+
'powerSwitch': int(toggle),
2192+
'switchIdx': 0
2193+
},
2194+
'method': 'setSwitch',
2195+
'source': 'APP'
2196+
}
2197+
2198+
r, _ = Helpers.call_api(
2199+
'/cloud/v2/deviceManaged/bypassV2',
2200+
method='post',
2201+
headers=head,
2202+
json_object=body,
2203+
)
2204+
2205+
if r is not None and Helpers.code_check(r):
2206+
if toggle:
2207+
self.device_status = 'on'
2208+
else:
2209+
self.device_status = 'off'
2210+
2211+
return True
2212+
logger.debug("Error toggling humidifier - %s", self.device_name)
2213+
return False
2214+
2215+
def set_humidity(self, humidity: int) -> bool:
2216+
"""Set target Humidifier humidity."""
2217+
if humidity < 30 or humidity > 80:
2218+
logger.debug("Humidity value must be set between 30 and 80")
2219+
return False
2220+
head, body = self.build_api_dict('setTargetHumidity')
2221+
2222+
if not head and not body:
2223+
return False
2224+
2225+
body['payload']['data'] = {
2226+
'targetHumidity': humidity
2227+
}
2228+
2229+
r, _ = Helpers.call_api(
2230+
'/cloud/v2/deviceManaged/bypassV2',
2231+
method='post',
2232+
headers=head,
2233+
json_object=body,
2234+
)
2235+
2236+
if r is not None and Helpers.code_check(r):
2237+
return True
2238+
logger.debug('Error setting humidity')
2239+
return False
2240+
2241+
def set_automatic_stop(self, mode: bool) -> bool:
2242+
"""Set Humidifier to automatic stop."""
2243+
if mode not in (True, False):
2244+
logger.debug(
2245+
'Invalid mode passed to set_automatic_stop - %s', mode)
2246+
return False
2247+
2248+
head, body = self.build_api_dict('setAutoStopSwitch')
2249+
if not head and not body:
2250+
return False
2251+
2252+
body['payload']['data'] = {
2253+
'autoStopSwitch': int(mode)
2254+
}
2255+
2256+
r, _ = Helpers.call_api(
2257+
'/cloud/v2/deviceManaged/bypassV2',
2258+
method='post',
2259+
headers=head,
2260+
json_object=body,
2261+
)
2262+
2263+
if r is not None and Helpers.code_check(r):
2264+
return True
2265+
if isinstance(r, dict):
2266+
logger.debug('Error toggling automatic stop')
2267+
else:
2268+
logger.debug('Error in api return json for %s', self.device_name)
2269+
return False

0 commit comments

Comments
 (0)