skydiver / ewelink-api

eWeLink API for JavaScript
https://www.npmjs.com/package/ewelink-api
MIT License
265 stars 109 forks source link

Cannot toggleDevice (or getPowerState) #89

Open JabLuszko opened 4 years ago

JabLuszko commented 4 years ago

I can login, get device list for more info, however I cannot toggleDevice - the ewelink API returns HTTP 500, however payload/URL seems correct according to theirs docs in https://github.com/CoolKit-Technologies/apiDocs/blob/master/en/APICenter.md#http-update-device-status Sample code:

const ewelink = require('ewelink-api');

(async () => {
  const connection = new ewelink({
    email: 'validemail@in.valid.domain',
    password: 'validpassword',
    region: 'eu',
  });

  /* get all devices */
  const devices = await connection.getDevices();
  console.log(devices);

  for (device in devices) {
       console.log("Name: " + devices[device]["name"]);
       console.log("DeviceID: " + devices[device]["deviceid"]);
       console.log("ProductModel: " + devices[device]["productModel"]);
       const device_info = await connection.getDevice(devices[device]["deviceid"]);
       console.log(device_info["productModel"]);
       if (device_info["productModel"] === "BASICR2") {
           console.log("Toggling this one")
           const status = await connection.toggleDevice(devices[device]["deviceid"]);
           console.log(status);
       }
  }
})();

I've added some console.log to see what is happening and when calling toggleDevice I can see it's successfully asking for device state (via const device = await this.getDevice(deviceId);) and correctly resolving the switch state (on/off), but when actually trying to turn it off/on the ewelink API ends with HTTP 500.

URL: https://eu-api.coolkit.cc:8080/api/user/device/status
PAYLOAD:
{ method: 'post',
  headers:
   { Authorization: 'Bearer <validauthorizationthatwasusedfewtimesalready>',
     'Content-Type': 'application/json' },
  body:
   '{"deviceid":"<validdeviceidfromloop>","params":{"switch":"off"},"appid":"YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q","nonce":"itj22djr","ts":1590686206,"version":8}' }
SERVER_RESP: <h2>Internal Server Error, real status: 500</h2>
(node:23243) UnhandledPromiseRejectionWarning: FetchError: invalid json response body at https://eu-api.coolkit.cc:8080/api/user/device/status reason: Unexpected token < in JSON at position 0
    at node_modules/node-fetch/lib/index.js:273:32
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:23243) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:23243) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

It could be (somehow?) worth noticing that this device is shared to this ewelink account. I also see a lot of strange things with ewelink API - "fd is null" or "device not found" when asking for getDevice (however they were just successfully listed via getDevices). However after few fails if I open original ewelink app it starts to report correct data for getDevice (I guess normal apps calls something more and populate (?) theirs cache)

EDIT Just tested on "main" (not shared to) ewelink account - same behavior - HTTP 500. So I would assume "shared" device is not an issue here.

$ npm list
└─┬ ewelink-api@3.0.0
  ├─┬ arpping@0.3.1 (github:skydiver/arpping#ae65410343bdcbddb64b37ac9f674c65af1fe92c)
  │ ├── child_process@1.0.2
  │ └── os@0.1.1
  ├── crypto-js@4.0.0
  ├── delay@4.3.0
  ├── node-fetch@2.6.0
  ├─┬ random@2.2.0
  │ ├─┬ babel-runtime@6.26.0
  │ │ ├── core-js@2.6.11
  │ │ └── regenerator-runtime@0.11.1
  │ ├── ow@0.4.0
  │ ├── ow-lite@0.0.2
  │ └── seedrandom@3.0.5
  ├─┬ websocket@1.0.31
  │ ├─┬ debug@2.6.9
  │ │ └── ms@2.0.0
  │ ├─┬ es5-ext@0.10.53
  │ │ ├─┬ es6-iterator@2.0.3
  │ │ │ ├─┬ d@1.0.1
  │ │ │ │ ├── es5-ext@0.10.53 deduped
  │ │ │ │ └── type@1.2.0
  │ │ │ ├── es5-ext@0.10.53 deduped
  │ │ │ └── es6-symbol@3.1.3 deduped
  │ │ ├─┬ es6-symbol@3.1.3
  │ │ │ ├── d@1.0.1 deduped
  │ │ │ └─┬ ext@1.4.0
  │ │ │   └── type@2.0.0
  │ │ └── next-tick@1.0.0
  │ ├── nan@2.14.1
  │ ├─┬ typedarray-to-buffer@3.1.5
  │ │ └── is-typedarray@1.0.0
  │ └── yaeti@0.0.6
  └─┬ websocket-as-promised@1.0.1
    ├── chnl@1.2.0
    ├── promise-controller@1.0.0
    └─┬ promise.prototype.finally@3.1.2
      ├─┬ define-properties@1.1.3
      │ └── object-keys@1.1.1
      ├─┬ es-abstract@1.17.5
      │ ├─┬ es-to-primitive@1.2.1
      │ │ ├── is-callable@1.1.5 deduped
      │ │ ├── is-date-object@1.0.2
      │ │ └─┬ is-symbol@1.0.3
      │ │   └── has-symbols@1.0.1 deduped
      │ ├── function-bind@1.1.1 deduped
      │ ├─┬ has@1.0.3
      │ │ └── function-bind@1.1.1 deduped
      │ ├── has-symbols@1.0.1
      │ ├── is-callable@1.1.5
      │ ├─┬ is-regex@1.0.5
      │ │ └── has@1.0.3 deduped
      │ ├── object-inspect@1.7.0
      │ ├── object-keys@1.1.1 deduped
      │ ├─┬ object.assign@4.1.0
      │ │ ├── define-properties@1.1.3 deduped
      │ │ ├── function-bind@1.1.1 deduped
      │ │ ├── has-symbols@1.0.1 deduped
      │ │ └── object-keys@1.1.1 deduped
      │ ├─┬ string.prototype.trimleft@2.1.2
      │ │ ├── define-properties@1.1.3 deduped
      │ │ ├── es-abstract@1.17.5 deduped
      │ │ └─┬ string.prototype.trimstart@1.0.1
      │ │   ├── define-properties@1.1.3 deduped
      │ │   └── es-abstract@1.17.5 deduped
      │ └─┬ string.prototype.trimright@2.1.2
      │   ├── define-properties@1.1.3 deduped
      │   ├── es-abstract@1.17.5 deduped
      │   └─┬ string.prototype.trimend@1.0.1
      │     ├── define-properties@1.1.3 deduped
      │     └── es-abstract@1.17.5 deduped
      └── function-bind@1.1.1
ahmed-essawy commented 4 years ago

I got the same error when trying to use "getDevicePowerState", "setDevicePowerState" or "toggleDevice"

this is the error (node:12460) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2) (node:12460) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

skydiver commented 4 years ago

Hello @ahmed-essawy

Can you post a sample code?

Also, there is any info about the file and line number where the errors is thrown?

ahmed-essawy commented 4 years ago

Hello @ahmed-essawy

Can you post a sample code?

Also, there is any info about the file and line number where the errors is thrown?

Hello @skydiver, The root of the error is coming from the endpoint itself if you try the following method you will get 500 internal server error

URL: https://eu-api.coolkit.cc:8080/api/user/device/status
PAYLOAD:
{ method: 'post',
  headers:
   { Authorization: 'Bearer <token>',
     'Content-Type': 'application/json' },
  body:
   '{"deviceid":"<deviceid>","params":{"switch":"off"},"appid":"YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q","nonce":"itj52slr","ts":1590686206,"version":8}' }
ahmed-essawy commented 4 years ago

Hello @skydiver , Did you get a chance to look into this API issue? I tried many changes on body object but all my trials fail :(, the endpoint itself has a problem

image

Thanks

skydiver commented 4 years ago

Hey @ahmed-essawy

Can you take a look to this thread and see if it helps?

The idea is to use custom APP_ID and APP_SECRET and see if this resolves your problems.

ianhampton commented 4 years ago

I see the same issue. I have only two devices on my account/network, one is a Sonoff one is from Kingart. Without the APP_ID and APP_SECRET that you recently shared in the other thread I can only see the Sonoff device when listing devices, with the ID/secret in place I can see both but get the same error from the API when trying to alter their state.

Using Zeroconf I can control the Sonoff locally, but not the Kingart.

JabLuszko commented 4 years ago

The idea is to use custom APP_ID and APP_SECRET and see if this resolves your problems.

No, APP_ID and APP_SECRET provided in that thread do not allow us to toggle devices, it still ends with HTTP ERROR 500 from coolkit.cc API. I would guess this is related to them going "paid" version and those APP_ID are not allowed to toggle via HTTP. Does it even work for you @skydiver, have you tried your own device?

I know that this SECRETs are provided straight from company, but seems like they are still limited - could be miss configuration on theirs side or just lack of communication how they were supposed to work. I don't really expect to restart non-sonoff devices, but at least the sonoff one. Could you hit them via that e-mail they asked you to contact and try to get some insight? https://github.com/CoolKit-Technologies/apiDocs/blob/master/en/Pricing.md

ahmed-essawy commented 4 years ago

Thanks @skydiver , I tried the fix but, unfortunately, the same error still happen

skydiver commented 4 years ago

@JabLuszko i don't have problems with my devices (all are sonoff)

Have you tried with the old parameters before the change? APP_ID: oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq APP_SECRET: 6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM

Also, in version 3, setDevicePowerState is using regular http requests (instead of websocket) to set power state, maybe you can checkout version 2 and see if works.

JabLuszko commented 4 years ago
//const APP_ID = 'YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q';
//const APP_SECRET = '4G91qSoboqYO4Y0XJ0LPPKIsq8reHdfa';
const APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq';
const APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM';

Both combos are not working, HTTP 500 from API, just like ticket states.

Yes, it does work from v2 and websockets. No, it does still do not work from v3 after working in v2 (like if it would update something in toolkit backend or something).

This really looks like APP_ID+APP_SECRET is limited by Sonoff guys. Your account/device is probably whitelisted or something(?).

skydiver commented 4 years ago

@JabLuszko i don't think my account has something special ...

restoring setDevicePowerState from v2 using websocket will work for you?

i still prefer the new method because it's a single http request while the socket version do many calls to the server, but i'm fine having both methods available

skydiver commented 4 years ago

Hey @JabLuszko

Can you try this branch release/3.1.0-ws ?

Here is a sample:

const connection = new ewelink({
  email: 'YOUR EMAIL',
  password: 'YOUR PASSWORD',
  APP_ID: 'CUSTOM APP ID',
  APP_SECRET: 'CUSTOM APP SECRET',
});

await connection.setWSDevicePowerState('YOUR DEVICE ID, 'toggle', {
  shared: true,
});

If your device is shared by a main account, you need to specify the shared: true, if not, can be safely removed.

ttz642 commented 4 years ago

@skydiver I'm having the same problem with the http version in v3 where the v2 versions worked ok.

ianhampton commented 4 years ago

I just switched to release/3.1.0-ws

Initially I got the same API error using setDevicePowerState(), I then tried setWSDevicePowerState() and I can successfully toggle my non-Sonoff switch!

ttz642 commented 4 years ago

@skydiver I've switched to 3.1.0-ws and it's working ok with setWSDevicePowerState().

skydiver commented 4 years ago

Thanks @ianhampton and @ttz642

I think setWSDevicePowerState is slower than setDevicePowerState, but resolves many problems for lot of people, so they will live together.

Will add the necessary docs then prepare the release.

Thanks to all who have helped on this one :tada

ttz642 commented 4 years ago

@skydiver getDevicePowerState fails with:

{"error":401,"errmsg":"no authorization"}

Will you be implementing a websocket version, ie: getWSDevicePowerState() ?

skydiver commented 4 years ago

@ttz642 yes, it's called getWSDevicePowerState

you can test on the same branch, docs are still missing, but this is an example:

  const response = await connection.getWSDevicePowerState(deviceId, {
    // channel: 3,
    // allChannels: true,
    shared: true,
  });
  console.log(response);

options:

let me know how it works so we can merge that branch and prepare the 3.1 release ...

ttz642 commented 4 years ago

Martin, I see you just added, I did grep for getWSDevicePowerState in the module but it wasn't there, resync'd this morning and changed over to using it and it works fine. One observation is it is slower but I'm guessing this is because the connection is closed and reopened each call. Rather than closing the websocket could you just leave it open and check if it's still open and if not re-open? For me the times I use it isn't an issue but it could be for some applications. It would be good if we could read the device state over the lan using ZeroConfig. On Wed, 2020-06-17 at 20:13 -0700, Martin M. wrote:

@ttz642 yes, it's called getWSDevicePowerState you can test on the same branch, docs are still missing, but this is an example: const response = await connection.getWSDevicePowerState(deviceId, { // channel: 3, // allChannels: true, shared: true, }); console.log(response); options:

channel: returns status of that particular channel allChannels: return state of all channels on multi-channel devices shared: if you're trying to control a share device (using a secondary account)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

ChocolateFrogsNuts commented 3 years ago

It appears the device status endpoint is broken (and possibly others too)... https://us-api.coolkit.cc:8080/api/user/device/status was working for me a week or two ago, and occasionally will still work, but now it mostly responds with a redirect to https://us-apia.coolkit.cc/ro - which responds with a 503. that second address is on the server for the V2 API, but is invalid.

I've been working on a Perl module similar to this and after spending today migrating it to the V2 API it works reliably. The changes aren't too extreme so it might be worth going there with this project.

skydiver commented 3 years ago

Release 4.0.0 it's under development: https://github.com/skydiver/ewelink-api/pull/113

Will use API v2.0