Closed jddayley closed 2 years ago
@jddayley Thank you for the captures. Can you please capture the device list and all functions with a description of what the function is by each capture? I'll add this on when you send them over
Thanks for the quick response. I wrote a python script that performs a few functions, turn on/off, set target humidity and get details. You'll easily see the payloads within each function. Please note the user agent is important because they seem to be filtering on it.
from os import pardir import requests, json, time, datetime, io, hashlib from random import randint from datetime import datetime from urllib.request import urlopen import urllib3
urllib3.disable_warnings() BASE_URL = "https://smartapi.vesync.com" ts = time.time() st = datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') token = "" accountID = "" import logging
logging.basicConfig( filename='vesync.log', filemode='a', format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level=logging.DEBUG)
class VesyncApi: def init(self, username, password): global token global accountID
payload = json.dumps({
"account":
username,
"devToken":
"",
"password":
hashlib.md5(password.encode('utf-8')).hexdigest()
})
account = requests.post(BASE_URL + "/vold/user/login",
verify=False,
data=payload).json()
if "error" in account:
raise RuntimeError("Invalid username or password")
else:
self._account = account
#logging.info (account)
token = account['tk']
#logging.info (token)
accountID = account['accountID']
#logging.info (accountID)
logging.info('Account Connection Successful')
self._devices = []
def get_detail(self, id):
global token
global accountID
payload = {
"acceptLanguage": "en",
"accountID": accountID,
"appVersion": "VeSync 3.1.54 build10",
"cid": id,
"configModule": "WFON_AHM_LUH-A602S-WUS_US",
"deviceRegion": "US",
"method": "bypassV2",
"payload": {
"data": {},
"method": "getHumidifierStatus",
"source": "APP"
},
"phoneBrand": "iPhone",
"phoneOS": "iOS 15.1.1",
"timeZone": "America/New_York",
"token": token,
"traceId": "1639257589508",
"userCountryCode": "US"
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
r = requests.post(BASE_URL + '/cloud/v2/deviceManaged/bypassV2',
headers=headers,
json=payload)
return r
def get_devices(self, id):
global token
global accountID
payload = {
"acceptLanguage": "en",
"accountID": accountID,
"appVersion": "VeSync 3.1.54 build10",
"cid": id,
"method": "deviceInfo",
"phoneBrand": "iPhone",
"phoneOS": "iOS 15.1.1",
"subDeviceNo": 0,
"timeZone": "America/New_York",
"token": token,
"traceId": "1639257579506",
"userCountryCode": "US"
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
r = requests.post(BASE_URL + '/cloud/v1/deviceManaged/deviceInfo',
headers=headers,
json=payload)
return r
def turn_off(self, id):
payload = {
"acceptLanguage": "en",
"accountID": accountID,
"appVersion": "VeSync 3.1.54 build10",
"cid": id,
"configModule": "WFON_AHM_LUH-A602S-WUS_US",
"debugMode": False,
"deviceRegion": "US",
"method": "bypassV2",
"payload": {
"data": {
"enabled": False,
"id": 0
},
"method": "setSwitch",
"source": "APP"
},
"phoneBrand": "iPhone",
"phoneOS": "iOS 15.1.1",
"timeZone": "America/New_York",
"token": token,
"traceId": "1639267601802",
"userCountryCode": "US"
}
headers = {
'Content-Type':
'application/json',
'Accept':
'*/*',
'user-agent':
'VeSync/3.1.54 (com.etekcity.vesyncPlatform; build:10; iOS 15.1.1) Alamofire/5.2.1'
}
r = requests.put(BASE_URL + '/cloud/v2/deviceManaged/bypassV2',
headers=headers,
json=payload)
logging.info(r.text)
def turn_on(self, id):
payload = {
"acceptLanguage": "en",
"accountID": accountID,
"appVersion": "VeSync 3.1.54 build10",
"cid": id,
"configModule": "WFON_AHM_LUH-A602S-WUS_US",
"debugMode": False,
"deviceRegion": "US",
"method": "bypassV2",
"payload": {
"data": {
"enabled": True,
"id": 0
},
"method": "setSwitch",
"source": "APP"
},
"phoneBrand": "iPhone",
"phoneOS": "iOS 15.1.1",
"timeZone": "America/New_York",
"token": token,
"traceId": "1639267601802",
"userCountryCode": "US"
}
headers = {
'Content-Type':
'application/json',
'Accept':
'*/*',
'user-agent':
'VeSync/3.1.54 (com.etekcity.vesyncPlatform; build:10; iOS 15.1.1) Alamofire/5.2.1'
}
r = requests.put(BASE_URL + '/cloud/v2/deviceManaged/bypassV2',
headers=headers,
json=payload)
logging.info(r.text)
def set_target(self, id, target):
payload = {
"acceptLanguage": "en",
"accountID": accountID,
"appVersion": "VeSync 3.1.54 build10",
"cid": id,
"configModule": "WFON_AHM_LUH-A602S-WUS_US",
"debugMode": False,
"deviceRegion": "US",
"method": "bypassV2",
"payload": {
"data": {
"target_humidity": target
},
"method": "setTargetHumidity",
"source": "APP"
},
"phoneBrand": "iPhone",
"phoneOS": "iOS 15.1.1",
"timeZone": "America/New_York",
"token": token,
"traceId": "1639267601802",
"userCountryCode": "US"
}
headers = {
'Content-Type':
'application/json',
'Accept':
'*/*',
'user-agent':
'VeSync/3.1.54 (com.etekcity.vesyncPlatform; build:10; iOS 15.1.1) Alamofire/5.2.1'
}
r = requests.put(BASE_URL + '/cloud/v2/deviceManaged/bypassV2',
headers=headers,
json=payload)
logging.info(r.text)
@jddayley How did you do original capture? It looks like VeSync started to use certificate pinning
I'm using https://mitmproxy.org with IOS. I am able to see the traffic as opposed to other apps that have certificate pinning.
I just bought one of these. I have Packet Capture installed on my phone.
This would be my first time intercepting/recording requests, I work in technology though and wouldn't necessarily be drowning in confusion :)
Any update on this?
Any chance anyone could capture the url's and payloads used to set warm_mode to enabled as well as the warm_level for the 600S?
Please integrate this, waiting for this on my home assistant server :D ❤️
I added a PR to add my 600s. If you all want to take a look and try it out, it should be straight forward. https://github.com/webdjoe/pyvesync/pull/110
Any chance anyone could capture the url's and payloads used to set warm_mode to enabled as well as the warm_level for the 600S?
I have a 600s and can capture packets. Where in the app is the warm level? I am not seeing it in mine.
@brianhealey This issue is for the LV_600S humidifier, not the Core600S air purifier.
Ah! Fair Enough. :)
For what its worth, I have an LV_600S on order and will be able to put a PR together for that once I have it.
I added a PR: https://github.com/webdjoe/pyvesync/pull/112
Any chance anyone could capture the url's and payloads used to set warm_mode to enabled as well as the warm_level for the 600S?
FYI, enabled and disabled is automatically set based upon level being 0 or higher.
@anoblet Would you mind playing with my PR and seeing if it does was you want? @dag81 . I hope this PR has what you are looking for.
Sure! What's the best way for me to override pyvesync
? Do I just clone it inside of custom_components
?
I haven't got that far. :) I tested it using a locally written cli, but I don't have it integrated with other tools.
I was able to use the CLI and got this response:
pprint(vars(fan))
{'cid': 'XXXXX',
'config': {'auto_target_humidity': 45,
'automatic_stop': True,
'display': False},
'config_module': 'WFON_AHM_LUH-A602S-WUS_US',
'connection_status': 'online',
'connection_type': 'WiFi+BTOnboarding+BTNotify',
'current_firm_version': None,
'details': {'automatic_stop_reach_target': False,
'display': False,
'humidity': 42,
'humidity_high': False,
'mist_level': 2,
'mist_virtual_level': 5,
'mode': 'humidity',
'warm_enabled': False,
'warm_level': 0,
'water_lacks': False,
'water_tank_lifted': False},
'device_image': 'https://image.vesync.com/defaultImages/LV_600S_Series/icon_lv600s_humidifier_160.png',
'device_name': 'Bedroom Humidifier',
'device_status': 'on',
'device_type': 'LUH-A602S-WUS',
'enabled': True,
'extension': None,
'mac_id': 'XXXXX',
'manager': <pyvesync.vesync.VeSync object at 0x000001BD4F03AB90>,
'mode': None,
'night_light': False,
'speed': None,
'sub_device_no': None,
'type': 'wifi-air',
'uuid': 'XXXXX',
'warm': True}
print(fan.displayJSON())
{
"Device Name": "Bedroom Humidifier",
"Model": "LUH-A602S-WUS",
"Subdevice No": "None",
"Status": "on",
"Online": "online",
"Type": "wifi-air",
"CID": "XXXXX",
"Mode": "humidity",
"Humidity": "44",
"Mist Virtual Level": "5",
"Mist Level": "2",
"Water Lacks": false,
"Humidity High": false,
"Water Tank Lifted": false,
"Display": false,
"Automatic Stop Reach Target": false,
"Auto Target Humidity": "45",
"Automatic Stop": true,
"Mist Warm Enabled": false,
"Mist Warm Level": 0
}
Are the properties on the device object supposed to be in sync with those from details? I ask because my humidifier is set to warm = 0
as reflected in the details attribute though on the device object it's set to true. Speed, nightlight, and mode either don't exist for this device, or don't seem to be in sync with the device state.
:) What you are seeing on the device is a "feature flag". The code uses this feature flag to determine if it should report and allow changes to the warmth setting. This is how nightlight works with other humidifiers.
I am not sure I follow your comments. I see mode listed.
"Mode": "humidity", "Automatic Stop Reach Target": false, "Auto Target Humidity": "45", "Mist Virtual Level": "5", "Mist Level": "2", "Display": false,
The mode can be set using mode_toggle
however I do see that it doesn't support humidity
which I can fix.
I imagine by "speed" you are referring to the mist level? That should be able to be toggled using the API.
I don't see a nightlight mode in the app, all that I see is "on/off" for the display. That can be set using set_display
.
Sorry for delay - I refactored the library to make it easier to add devices if you can please test - https://github.com/webdjoe/pyvesync/tree/air-refactor
Can anyone give this PR #121 a test for the LV600S?
This should be fixed in #121 Please make a new issue if you are still having trouble.
I would like to request supporter the Levoit LV_600S Humidifier. I install a MITM proxy and captured the following dumps. Let me know if you any additional payloads.
{ "code": 0, "msg": "request success", "result": { "cidFwInfoList": [ { "code": 0, "configModule": "WFON_AHM_LUH-A602S-WUS_US", "connectionType": "WiFi+BTOnboarding+BTNotify", "deviceCid": "vsaq7588f6f84290868639af8c171175", "deviceImg": "https://image.vesync.com/defaultImages/LV_600S_Series/icon_lv600s_humidifier_160.png", "deviceName": "Humidifier ", "deviceRegion": "US", "firmUpdateInfos": [ { "currentVersion": "1.0.42", "isMainFw": false, "latestVersion": "1.0.00", "latestVersionUrl": "http://firm-testonline.vesync.com:4005/firm/amazon/WFON_AHM_LUH-A602S-WUS_US/v1.0.00/", "partFirmwareVersionUrl": "", "pluginName": "mcuFw", "priority": 0, "releaseNotes": "First Version.", "upgradeLevel": 0, "upgradeTimeoutInSec": 120 }, { "currentVersion": "1.0.07", "isMainFw": true, "latestVersion": null, "latestVersionUrl": null, "partFirmwareVersionUrl": null, "pluginName": "mainFw", "priority": 1, "releaseNotes": null, "upgradeLevel": 0, "upgradeTimeoutInSec": 120 } ], "macID": "24:d7:eb:01:af:02", "msg": null, "uuid": "cb5c8675-a00e-40a6-bc70-55b37b2ab7da" } ], "macIDFwInfoList": null }, "traceId": "1639054148013"
{ "code": 0, "msg": "request success", "result": { "code": 0, "result": { "automatic_stop_reach_target": true, "configuration": { "auto_target_humidity": 50, "display": true }, "display": true, "enabled": true, "extension": { "schedule_count": 0, "timer_remain": 0 }, "humidity": 52, "humidity_high": false, "mist_level": 3, "mist_virtual_level": 9, "mode": "humidity", "warm_enabled": true, "warm_level": 3, "water_lacks": false, "water_tank_lifted": false }, "traceId": "1639054152557" }, "traceId": "1639054152557"