ewelink-api-python icon indicating copy to clipboard operation
ewelink-api-python copied to clipboard

This is great - could maybe have more in the readme.md ...

Open ajfhodgson opened this issue 3 years ago • 33 comments

Not an issue - just some kudos!

I had used https://github.com/skydiver/ewelink-api successfully in node/javascript, but needed a Python version.

Initially I found https://github.com/lucien2k/sonoff-python and spent a day failing to make it work.

But this port just worked!

It could maybe do with a few more instructions in the readme, to indicate how very easy this is to make work, e.g.

import ewelink
import json
email = 'your_email'
password = 'your_password'
region = 'eu' # or 'us' or 'cn'
connection = ewelink.Ewelink(email, password, region)
devices = connection.getDevices()
for device in devices['devicelist']:
    print(f'{device["deviceid"]}, {device["name"]}')
resp = connection.getDevice(devices['devicelist'][0]["deviceid"])
print(f'{json.dumps(resp)}')

Boom! Excellent!

This deserves to be better known.

ajfhodgson avatar Feb 01 '22 11:02 ajfhodgson

Using this simple script I have got this errors, at least Im logged in :)

[root@testowy ewelink-api-python]# python3 test.py
{'log': 'ok', 'status': 'success'}
Traceback (most recent call last):
  File "test.py", line 7, in <module>
    devices = connection.getDevices()
  File "/root/ewelink-api-python/ewelink/ewelink.py", line 28, in getDevices
    return Object(res.json())
  File "/root/ewelink-api-python/ewelink/models/object.py", line 7, in __init__
    self.__recurse_to_self()
  File "/root/ewelink-api-python/ewelink/models/object.py", line 44, in __recurse_to_self
    l = list(map(Object, value.copy()))
  File "/root/ewelink-api-python/ewelink/models/object.py", line 7, in __init__
    self.__recurse_to_self()
  File "/root/ewelink-api-python/ewelink/models/object.py", line 44, in __recurse_to_self
    l = list(map(Object, value.copy()))
  File "/root/ewelink-api-python/ewelink/models/object.py", line 5, in __init__
    super().__init__(data)
ValueError: dictionary update sequence element #0 has length 1; 2 is required

PJanisio avatar Mar 31 '22 17:03 PJanisio

@PJanisio and @ajfhodgson well i have a lot of work, that's why im not able to focus on it. Janisio you might have not got that error if u would have used the repo before the latest commit which broke stuff unknowingly cause i left somethings incomplete. 😔 Well im planning an asynchronous re-write of this. That will make it stable and accurate.

AceExpert avatar Mar 31 '22 18:03 AceExpert

Dont let it die :)

PJanisio avatar Mar 31 '22 18:03 PJanisio

I have done an asynchronous re write of this. Its under development still, check out the new examples.

AceExpert avatar Apr 16 '22 09:04 AceExpert

And, this isn't a port anymore, its a independent wrapper around the API. Independent of what ewelink-api does. It will have its own features. I cannot assure anything about its future

AceExpert avatar Apr 16 '22 09:04 AceExpert

I appreciate the time these things soak up!

Well, the version I downloaded continues to successfully poll two humidity sensors under my house every 30 minutes, and has since January, 2022, so I have no reason to upgrade until that stops working! :-)

To encourage you, here's what you've enabled for me: image

Red is humidity outside the house (from openweather.org), purple and green are under my house. Blue is amount of rain (London, UK) - it's been a VERY dry spring!

ajfhodgson avatar Apr 16 '22 11:04 ajfhodgson

Thanks for udpates, but damn im newbie at python, so maybe you can take a look at this syntax error I got:

[root@testowy ewelink-api-python]# python3 test.py
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    import ewelink
  File "/root/ewelink-api-python/ewelink/__init__.py", line 1, in <module>
    from .client import Client, login
  File "/root/ewelink-api-python/ewelink/client.py", line 14, in <module>
    from .models import ClientUser, Device, Devices, Region
  File "/root/ewelink-api-python/ewelink/models/__init__.py", line 2, in <module>
    from .user import AppInfo, ClientInfo, ClientUser
  File "/root/ewelink-api-python/ewelink/models/user.py", line 47
    if extra := data.get('extra', None):
              ^
SyntaxError: invalid syntax

PJanisio avatar Apr 21 '22 10:04 PJanisio

@PJanisio make sure your Python version is 3.9+

sputh-the-pigeon avatar Apr 21 '22 10:04 sputh-the-pigeon

@PJanisio make sure your Python version is 3.9+

Damn, mine is 3.6.8 and unfortunatelly I can not update it to 3.9 so far completely. I think i need to run it in its own 3.9 environment.

PJanisio avatar Apr 21 '22 10:04 PJanisio

Hmmm... Python assignment is simply extra = data.get('extra', None) no colon!!!

ajfhodgson avatar Apr 21 '22 10:04 ajfhodgson

Well okay, Python 3.9.6 installed, credentials are ok -sorry to bother :)

[root@testowy ewelink-api-python]# python3 test.py
Traceback (most recent call last):
  File "/www/python/ewelink-api-python/test.py", line 1, in <module>
    import ewelink
  File "/www/python/ewelink-api-python/ewelink/__init__.py", line 1, in <module>
    from .client import Client, login
  File "/www/python/ewelink-api-python/ewelink/client.py", line 14, in <module>
    from .models import ClientUser, Device, Devices, Region
  File "/www/python/ewelink-api-python/ewelink/models/__init__.py", line 2, in <module>
    from .user import AppInfo, ClientInfo, ClientUser
  File "/www/python/ewelink-api-python/ewelink/models/user.py", line 6, in <module>
    from ..http import HttpClient
  File "/www/python/ewelink-api-python/ewelink/http.py", line 27, in <module>
    'email': str | None,
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'
[root@testowy ewelink-api-python]# python3 --version
Python 3.9.6

PJanisio avatar Apr 21 '22 15:04 PJanisio

Hmmm... Python assignment is simply extra = data.get('extra', None) no colon!!!

Sorry, but := is the walrus operator added in Python 3.8. You may check about them in the docs.

sputh-the-pigeon avatar Apr 24 '22 06:04 sputh-the-pigeon

@PJanisio i m not sure why that error occurs. Im not able to reproduce it

sputh-the-pigeon avatar Apr 24 '22 06:04 sputh-the-pigeon

@PJanisio oh damn, sorry im really sorry, this totally forgot. Its not python 3.9+ you need python 3.10+ for this library. Really very sorry for your troubles

sputh-the-pigeon avatar Apr 24 '22 06:04 sputh-the-pigeon

@PJanisio oh damn, sorry im really sorry, this totally forgot. Its not python 3.9+ you need python 3.10+ for this library. Really very sorry for your troubles

Don`t worry its not a priority :) There is a step forward - im using compiled python 3.10.4.

[root@testowy ewelink-api-python]# python3.10 test.py

Traceback (most recent call last):

  File "/www/python/ewelink-api-python/test.py", line 4, in <module>
    @ewelink.login('xxxxxxxxxx', 'xxxxxxxxx')

  File "/www/python/ewelink-api-python/ewelink/client.py", line 67, in login
    asyncio.get_event_loop().run_until_complete(client.login())

  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()

  File "/www/python/ewelink-api-python/ewelink/client.py", line 43, in login
    self.devices = Devices(

  File "/www/python/ewelink-api-python/ewelink/models/device.py", line 83, in __init__
    super().__init__(devices)

  File "/www/python/ewelink-api-python/ewelink/client.py", line 44, in <genexpr>
    Device(data = device, http = self.http, ws=self.ws) for device in (await self.http.get_devices()).get('devicelist', [])

  File "/www/python/ewelink-api-python/ewelink/models/device.py", line 49, in __init__
    self.state: PowerState = PowerState[data['params']['switch']]

KeyError: 'switch'

Maybe its a specific device issue. Below my devices list (model):

  • 4CH Pro
  • POWR2
  • BASICR2
  • Touch EU

PJanisio avatar Apr 24 '22 08:04 PJanisio

@PJanisio hmm true. I just have sonoff and i thought 'switch' should probably be common to all devices, didnt know it wasnt i will probably make a change such that this state becomes Optional

sputh-the-pigeon avatar Apr 24 '22 08:04 sputh-the-pigeon

I'd like to thank you for this great tool. I'm currently facing the same issue as "switch" key is not found. In my case it is called "switches". Here is the details I received for "param":

   "params":{
      "bindInfos":{
         "gaction":[
            "HERE IS SOME KIND OF A KEY"
         ]
      },
      "version":8,
      "switches":[
         {
            "switch":"off",
            "outlet":0
         },
         {
            "switch":"off",
            "outlet":1
         },
         {
            "switch":"off",
            "outlet":2
         },
         {
            "switch":"off",
            "outlet":3
         }
      ],

I only have Sonoff Micro installed in my account.

Cainor avatar Apr 27 '22 01:04 Cainor

So after playing around for a bit. I've fixed the issue by replacing the code found in ewelink\models\device.py:50 From:

self.state: Power = Power[data['params']['switch']]

To:

self.state: Power = Power[data['params']['switches'][0]['switch']]

Cainor avatar Apr 27 '22 14:04 Cainor

Oh man .. I'd love to contribute to this, seems like the code is written in an advance way that I never tried to type with. I'll try to learn something or two about asyncio maybe I can help :)

All the thanks to you 👍

Cainor avatar Apr 27 '22 15:04 Cainor

One thing I'm currently facing .. I always get asyncio.exceptions.TimeoutError when trying to change the status of a device ..

Traceback (most recent call last):
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 456, in wait_for
    return fut.result()
asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "c:\Users\USER\ewelink-api-python\sonoffy.py", line 6, in <module>
    async def main(client: Client):
  File "c:\Users\USER\ewelink-api-python\ewelink\client.py", line 69, in decorator
    result = asyncio.get_event_loop().run_until_complete(f(client))
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 646, in run_until_complete
    return future.result()
  File "c:\Users\USER\ewelink-api-python\sonoffy.py", line 19, in main
    await device.edit(Power.on)
  File "c:\Users\USER\ewelink-api-python\ewelink\models\device.py", line 68, in edit
    await self.ws.update_device_status(self.id,
  File "c:\Users\USER\ewelink-api-python\ewelink\ws.py", line 87, in update_device_status
    result = await asyncio.wait_for(fut, timeout = 30)
  File "C:\Users\USER\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 458, in wait_for
    raise exceptions.TimeoutError() from exc
asyncio.exceptions.TimeoutError

Cainor avatar Apr 27 '22 17:04 Cainor

getting this error, any idea how to resolve this?

File "test.py", line 30, in import ewelink File "/home/shishir/ewelink/init.py", line 1, in from .client import Client, login File "/home/shishir/ewelink/client.py", line 15, in from .models import ClientUser, Device, Devices, Region File "/home/shishir/ewelink/models/init.py", line 4, in from .user import AppInfo, ClientInfo, ClientUser File "/home/shishir/ewelink/models/user.py", line 6, in from .object import Object File "/home/shishir/ewelink/models/object.py", line 3, in class Object(dict): File "/home/shishir/ewelink/models/object.py", line 5, in Object def init(self, data: dict[Any, Any], name: str = "Object") -> None: TypeError: 'type' object is not subscriptable

Shisir99 avatar May 09 '22 08:05 Shisir99

@Shisir99 make sure you on Python 3.10+

sputh-the-pigeon avatar May 09 '22 13:05 sputh-the-pigeon

@PJanisio @Cainor the "switch" KeyError is fixed in the latest commit. Please update your libraries to the latest master branch of the repository.

@Cainor you can access the switches using:

switches = device.params.switches
print(switches[0].switch, switches[1].switch)

AceExpert avatar May 09 '22 13:05 AceExpert

@PJanisio @Cainor the "switch" KeyError is fixed in the latest commit. Please update your libraries to the latest master branch of the repository.

@Cainor you can access the switches using:

switches = device.params.switches
print(switches[0].switch, switches[1].switch)

Works like a charm ! :) So using example script from readme I can see devices data, but at the end of script I got this error message. Dunno what is NoneType device/object.

Traceback (most recent call last):
  File "/www/python/ewelink-api-python/test.py", line 5, in <module>
    async def main(client: Client):
  File "/www/python/ewelink-api-python/ewelink/client.py", line 94, in decorator
    result = asyncio.get_event_loop().run_until_complete(f(client))
  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/www/python/ewelink-api-python/test.py", line 12, in main
    print(device.params)
AttributeError: 'NoneType' object has no attribute 'params'

PJanisio avatar May 09 '22 19:05 PJanisio

@Shisir99 make sure you on Python 3.10+

@sputh-the-pigeon yes , I am on python 3.10+ although getting the error.

Shisir99 avatar May 10 '22 04:05 Shisir99

@PJanisio the device is None. Means the device ID you provided is invalid and no such device with that ID exists.

sputh-the-pigeon avatar May 10 '22 16:05 sputh-the-pigeon

@Shisir99 I do not get that error. Check if you have multiple python installations or probably any older version of python is running the code.

sputh-the-pigeon avatar May 10 '22 16:05 sputh-the-pigeon

you can access the switches using:

switches = device.params.switches
print(switches[0].switch, switches[1].switch)

Sure, this will work for accessing the state on/off as str, but switches can be turned off and on and current implementation seems to support only per device ON/OFF, not per switch - like for example 4 channel relays?

JabLuszko avatar May 11 '22 02:05 JabLuszko

@PJanisio the device is None. Means the device ID you provided is invalid and no such device with that ID exists.

Exactly, I have done mistype in device id. My bad :)

PJanisio avatar May 12 '22 10:05 PJanisio

@JabLuszko I'm aware. I haven't implemented those because of my exams. Until those are done, you may use:

await client.ws.update_device_status(deviceid, switches = ...)

This is raw websocket method, all per channel, per device on off methods are built on top of this only, here's the source: https://github.com/AceExpert/ewelink-api-python/blob/2c6305f5abf4874284a6fe48a327fe60020d5326/ewelink/ws.py#L71

sputh-the-pigeon avatar May 13 '22 12:05 sputh-the-pigeon