webdjoe / pyvesync

pyvesync is a python library to manage Etekcity & Levoit smart devices
MIT License
175 stars 83 forks source link

VeSyncHumid200300S.set_display has no effect on Classic 200S humidifier #114

Closed jspayd closed 2 years ago

jspayd commented 2 years ago

I'm working on a Home Assistant PR to add support for the Classic 300S humidifiers (https://github.com/home-assistant/core/pull/62907), and I'm adding the Classic 200S now that there's a new pyvesync release. Almost everything for the 200S works fine, but the display light toggle does not. Invoking set_display has no effect on the 200S humidifier, and details["display"] is always False for the Classic 200S. Everything for the 300S works just fine. Perhaps the 200S has a separate API for its display toggle since it just has a single light, whereas the 300S has a more detailed display?

NickM-27 commented 2 years ago

I have seen the same thing, the app does offer a switch for turning it on and off so it is likely a different API if I had to guess 🤔

webdjoe commented 2 years ago

I’m fixing this in the latest release, should be out in a couple of days.

On Wed, Feb 16, 2022 at 9:34 AM Nicolas Mowen @.***> wrote:

I have seen the same thing, the app does offer a switch for turning it on and off so it is likely a different API if I had to guess 🤔

— Reply to this email directly, view it on GitHub https://github.com/webdjoe/pyvesync/issues/114#issuecomment-1041555260, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6JJBXPPNGPDAYWJMNXXT3U3OYYRANCNFSM5OIEMR7Q . You are receiving this because you are subscribed to this thread.Message ID: @.***>

webdjoe commented 2 years ago

please test this branch, should be fixed - https://github.com/webdjoe/pyvesync/tree/air-refactor

NickM-27 commented 2 years ago

Could be an issue with the setup I have, but I am seeing an error that fan_modules does not exist in object_factory fun of vesync.py

webdjoe commented 2 years ago

Hmm, it worked with my air purifier. I updated it with a direct reference, please give it another shot.

NickM-27 commented 2 years ago

Okay I don't see any errors this time, None of the humidifiers were brought into the Home Assistant integration (I have the pyvesync dir important directly), but I figure that may be due to some changes needed on the HASS side? I am using my fork

NickM-27 commented 2 years ago

I realize testing in home assistant may not be the best way to go, I also tried running in VS code and I get this:

Traceback (most recent call last):
  File "/Users/nickmowen/PythonProjects/pyvesync/src/main.py", line 1, in <module>
    from pyvesync import VeSync
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/__init__.py", line 5, in <module>
    from .vesync import VeSync
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesync.py", line 8, in <module>
    from pyvesync.helpers import Helpers
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/helpers.py", line 6, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'
webdjoe commented 2 years ago

The interface should not have changed, try to test the branch with a simple script like in the readme. I haven't used HA in a while but when I was developing the component it was difficult to test new python packages. Had to use source and manually replace directory in site packages.

On Wed, Mar 9, 2022 at 12:02 PM Nicolas Mowen @.***> wrote:

Okay I don't see any errors this time, None of the humidifiers were brought into the Home Assistant integration (I have the pyvesync dir important directly), but I figure that may be due to some changes needed on the HASS side? I am using my fork https://github.com/NickM-27/HA_VeSync_Humidifiers/blob/main/humidifier.py

— Reply to this email directly, view it on GitHub https://github.com/webdjoe/pyvesync/issues/114#issuecomment-1063149838, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6JJBTM4VVOMPUXLFOXJX3U7DKT7ANCNFSM5OIEMR7Q . You are receiving this because you commented.Message ID: @.***>

webdjoe commented 2 years ago

pip install requests or pip3 install requests in the terminal. Make sure the right environment that is running the code is activated

On Wed, Mar 9, 2022 at 12:11 PM Nicolas Mowen @.***> wrote:

I realize testing in home assistant may not be the best way to go, I also tried running in VS code and I get this:

Traceback (most recent call last): File "/Users/nickmowen/PythonProjects/pyvesync/src/main.py", line 1, in from pyvesync import VeSync File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/init.py", line 5, in from .vesync import VeSync File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesync.py", line 8, in from pyvesync.helpers import Helpers File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/helpers.py", line 6, in import requests ModuleNotFoundError: No module named 'requests'

— Reply to this email directly, view it on GitHub https://github.com/webdjoe/pyvesync/issues/114#issuecomment-1063159032, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6JJBVUM2VY5XUMJY6LLD3U7DLU5ANCNFSM5OIEMR7Q . You are receiving this because you commented.Message ID: @.***>

NickM-27 commented 2 years ago

Ah yes, my bad lol. I am not sure the best way to give you debug information but I tried calling turn_off_display() on the Classic 200S and did not see anything happen. Testing on my Classic 300S and it still worked.

If it helps, I do not mind sharing my VeSync account over discord or something so you can debug directly, also happy to help if there is a way to get more info on it

NickM-27 commented 2 years ago

Actually.. something interesting! I can see in the API response that the display is set to False despite it being on in front of me. Perhaps VeSync is just borking it themselves. Will do some more fiddling and see what happens.

Edit: There is also a firmware update available for it so will try that and see if there is a differnce.

NickM-27 commented 2 years ago

Okay so if I use the app to turn the display on or off, it always comes back as False inside the response from device.display()

webdjoe commented 2 years ago

Try calling set_night_light(True) it might give you an error from wanting a string. If so try `set_night_light("true") or I will take off the type check

On Wed, Mar 9, 2022 at 12:28 PM Nicolas Mowen @.***> wrote:

Okay so if I use the app to turn the display on or off, it always comes back as False inside the response from device.display()

— Reply to this email directly, view it on GitHub https://github.com/webdjoe/pyvesync/issues/114#issuecomment-1063176127, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6JJBR5RFXBGBBHKWMWEP3U7DNTZANCNFSM5OIEMR7Q . You are receiving this because you commented.Message ID: @.***>

NickM-27 commented 2 years ago

I am getting this: AttributeError: 'VeSyncHumid200300S' object has no attribute 'set_night_light'

jspayd commented 2 years ago

I can take a closer look and test this myself later today, but the issue is with the display light; not the night light (which the 200S doesn’t have). Not sure if that was a misunderstanding of the issue or a typo in the latest comment.

webdjoe commented 2 years ago

That's my fault for the typo. It's hard for me to see what's going on since I don't have access to one. If you can capture packets using https://www.charlesproxy.com/ by installing the ssl certificate on an iphone I can help out further.

NickM-27 commented 2 years ago

I can take a closer look and test this myself later today, but the issue is with the display light; not the night light (which the 200S doesn’t have). Not sure if that was a misunderstanding of the issue or a typo in the latest comment.

If you look above I tried the display fun first and was then recommended to try the night light fun

jspayd commented 2 years ago

I got together a test script, which works for my 300S but not my 200S. I'm not seeing any errors printed. I'm not certain if I can expect the logger to print to the console, but I can guarantee no uncaught errors are thrown; there is just no effect. I'll try to spy on the packets to get more info.

jspayd commented 2 years ago

It looks like the method to turn the display on/off is setIndicatorLightSwitch and the key in the getHumidifierStatus response is indicator_light_switch. Also notable is that the setIndicatorLightSwitch method uses a different syntax in data than setDisplay: it uses the enabled/id syntax rather tan the state syntax.

Request to turn light on:

{
  "cid": "REDACTED",
  "userCountryCode": "US",
  "deviceRegion": "US",
  "method": "bypassV2",
  "token": "REDACTED",
  "payload": {
    "method": "setIndicatorLightSwitch",
    "source": "APP",
    "data": { "enabled": true, "id": 0 }
  },
  "accountID": "REDACTED",
  "traceId": "REDACTED",
  "phoneOS": "iOS 15.3.1",
  "configModule": "WiFiBTOnboardingNotify_AirHumidifier_Classic200S_US",
  "acceptLanguage": "en",
  "timeZone": "America/Los_Angeles",
  "phoneBrand": "iPhone XS",
  "debugMode": false,
  "appVersion": "VeSync 3.2.6 build6"
}

Request to turn light off:

{
  "accountID": "REDACTED",
  "acceptLanguage": "en",
  "phoneOS": "iOS 15.3.1",
  "timeZone": "America/Los_Angeles",
  "appVersion": "VeSync 3.2.6 build6",
  "configModule": "WiFiBTOnboardingNotify_AirHumidifier_Classic200S_US",
  "payload": {
    "data": { "enabled": false, "id": 0 },
    "source": "APP",
    "method": "setIndicatorLightSwitch"
  },
  "cid": "REDACTED",
  "method": "bypassV2",
  "traceId": "REDACTED",
  "deviceRegion": "US",
  "token": "REDACTED",
  "phoneBrand": "iPhone XS",
  "debugMode": false,
  "userCountryCode": "US"
}

getHumidifierStatus response when turned on:

{
  "traceId": "REDACTED",
  "code": 0,
  "msg": "request success",
  "result": {
    "traceId": "REDACTED",
    "code": 0,
    "result": {
      "enabled": true,
      "mist_virtual_level": 1,
      "mist_level": 1,
      "mode": "auto",
      "water_lacks": false,
      "water_tank_lifted": false,
      "humidity": 42,
      "humidity_high": false,
      "indicator_light_switch": true,
      "automatic_stop_reach_target": true,
      "configuration": {
        "auto_target_humidity": 40,
        "indicator_light_switch": true,
        "automatic_stop": true
      }
    }
  }
}

getHumidifierStatus response when turned off:

{
  "traceId": "REDACTED",
  "code": 0,
  "msg": "request success",
  "result": {
    "traceId": "REDACTED",
    "code": 0,
    "result": {
      "enabled": true,
      "mist_virtual_level": 1,
      "mist_level": 1,
      "mode": "auto",
      "water_lacks": false,
      "water_tank_lifted": false,
      "humidity": 42,
      "humidity_high": false,
      "indicator_light_switch": false,
      "automatic_stop_reach_target": true,
      "configuration": {
        "auto_target_humidity": 40,
        "indicator_light_switch": false,
        "automatic_stop": true
      }
    }
  }
}
jspayd commented 2 years ago

By replacing setDisplay with setIndicatorLightSwitch, { 'state': mode } with { 'enabled': mode, 'id': 0 }, and display with indicator_light_switch, I was able to control the display on my humidifier and get the display status (of course the simple replacements break the 300S; this was just a test).

webdjoe commented 2 years ago

Just pushed the fix, please let me know if there's still an issue

jspayd commented 2 years ago

Excellent, thanks! I’ll test it out tomorrow.

NickM-27 commented 2 years ago

@webdjoe What is the method that is supposed to be used to toggle the display? I see that the correct display status is being returned now, but I get this error when using turn_off_display

File "<stdin>", line 1, in <module>
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesyncfan.py", line 1059, in turn_off_display
    return self.set_display(False)
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesyncfan.py", line 1299, in set_display
    body['payload']['data'] = {
KeyError: 'payload'
jspayd commented 2 years ago

I'm seeing the same issue. It's because setIndicatorLightSwitch isn't in modes in build_api_dict.

webdjoe commented 2 years ago

My mistake, just pushed the fix

NickM-27 commented 2 years ago

Getting closer, now I see:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesyncfan.py", line 1059, in turn_off_display
    return self.set_display(False)
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesyncfan.py", line 1297, in set_display
    head, body = self.build_api_dict('setIndicatorLightSwitch')
  File "/Users/nickmowen/PythonProjects/pyvesync/src/pyvesync/vesyncfan.py", line 861, in build_api_dict
    raise ValueError
ValueError
jspayd commented 2 years ago

Oh sorry I missed the correspondence before my PR.

jspayd commented 2 years ago

@webdjoe It looks like you missed the Switch on the end of setIndicatorLightSwitch. I'm happy if you want to just add it to the supported modes, or there's also my PR which worked when I tested it. Thanks for responding so quickly.

webdjoe commented 2 years ago

Thank you @jspayd I was having more trouble than I should have. Merged

kevchu3 commented 2 years ago

I'm testing @webdjoe's air-refactor branch in #121 and from what I can see, set_display is working correctly for my Classic 200S, and updates are occurring in both the VeSync app and physically on the humidifier itself.

Testing notes:

>>> humidifier.device_type
'Classic200S'
>>> humidifier.update()
>>> humidifier.details
{'humidity': 45, 'mist_virtual_level': 1, 'mist_level': 1, 'mode': 'manual', 'water_lacks': False, 'humidity_high': False, 'water_tank_lifted': False, 'display': True, 'automatic_stop_reach_target': False, 'night_light_brightness': 0}
>>> humidifier.set_display(False)
True
>>> humidifier.update()
>>> humidifier.details
{'humidity': 45, 'mist_virtual_level': 1, 'mist_level': 1, 'mode': 'manual', 'water_lacks': False, 'humidity_high': False, 'water_tank_lifted': False, 'display': False, 'automatic_stop_reach_target': False, 'night_light_brightness': 0}
>>> humidifier.set_display(True)
True
>>> humidifier.update()
>>> humidifier.details
{'humidity': 45, 'mist_virtual_level': 1, 'mist_level': 1, 'mode': 'manual', 'water_lacks': False, 'humidity_high': False, 'water_tank_lifted': False, 'display': True, 'automatic_stop_reach_target': False, 'night_light_brightness': 0}
>>> humidifier.turn_off_display()
True
>>> humidifier.update()
>>> humidifier.details
{'humidity': 45, 'mist_virtual_level': 1, 'mist_level': 1, 'mode': 'manual', 'water_lacks': False, 'humidity_high': False, 'water_tank_lifted': False, 'display': False, 'automatic_stop_reach_target': False, 'night_light_brightness': 0}
>>> humidifier.turn_on_display()
True
>>> humidifier.update()
>>> humidifier.details
{'humidity': 45, 'mist_virtual_level': 1, 'mist_level': 1, 'mode': 'manual', 'water_lacks': False, 'humidity_high': False, 'water_tank_lifted': False, 'display': True, 'automatic_stop_reach_target': False, 'night_light_brightness': 0}