Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Any support of HS300 in the future? #27

Closed
montiquewillis opened this issue Mar 15, 2019 · 20 comments
Closed

Any support of HS300 in the future? #27

montiquewillis opened this issue Mar 15, 2019 · 20 comments

Comments

@montiquewillis
Copy link

montiquewillis commented Mar 15, 2019

I get back the device which is a PowerStrip in the device list. However, the object does not include an array of the underlying outlets. I understand this package doesn't include this device, just wanted to ask. I removed pertinent private values from the below JSON.

{ fwVer: "1.0.10 Build 190103 Rel.163517" deviceName: "Wi-Fi Smart Power Strip" alias: "TP-LINK_Power Strip_CDD2" appServerUrl: "https://use1-wap.tplinkcloud.com" deviceHwVer: "1.0" deviceId: "" deviceMac: "" deviceModel: "HS300(US)" deviceName: "Wi-Fi Smart Power Strip" deviceType: "IOT.SMARTPLUGSWITCH" fwId: "" fwVer: "" hwId: "" isSameRegion: true oemId: "" role: 0 status: 1 }

@adumont
Copy link
Owner

adumont commented Mar 15, 2019

I understand the HS300 is a strip of 6 independant plugs. How do you see it in the Kasa App? I mean can you set for example an alias for each of the 6 plug?
The sample output above seem to show only 1 device (1 deviceId you have stripped). Do you see 6 of these? or only 1 for the 6? That would help me understand (I don't have any HS300 unfortunately)

@montiquewillis
Copy link
Author

montiquewillis commented Mar 15, 2019

There are 6 of them in the app with individual switch and energy monitor data. In the app, you can set alias names for each outlet. I'm not able to see a way to change the alias for the strip itself via the app. When I use the getDeviceList() function that's the object I get back. I'm thinking it may be in the original payload, just not in the returned object here, but I could be wrong.

@athenawisdoms
Copy link

I am supporting the addition of HS300 support to this amazing library! Is there anything we owners of H300 can do to help with getting HS300 supported, short of doing the actual coding (which is beyond me right now)?

@ColinMcNeil
Copy link
Contributor

In tplink.ts, the code to get the device list is
return (this.deviceList = response.data.result.deviceList);.
My guess is that there is more information in response, or perhaps there is a different query to get each outlet. I'm guessing there is a 1-M db relationship somewhere that we have to pull from.

@atkinsj
Copy link

atkinsj commented Jun 7, 2020

Adding some additional info here for if you ever get around to it.

Here's what a hit to get the getDeviceList endpoint shows:

{
  "error_code": 0,
  "result": {
    "deviceList": [
      {
        "deviceType": "IOT.SMARTPLUGSWITCH",
        "role": 0,
        "fwVer": "1.0.19 Build 200224 Rel.090814",
        "appServerUrl": "https://use1-wap.tplinkcloud.com",
        "deviceRegion": "us-east-1",
        "deviceId": "redacted",
        "deviceName": "Wi-Fi Smart Power Strip",
        "deviceHwVer": "1.0",
        "alias": "TP-LINK_Power Strip_2A54",
        "deviceMac": "redacted",
        "oemId": "5C9E6254BEBAED63B2B6102966D24C17",
        "deviceModel": "HS300(US)",
        "hwId": "redacted",
        "fwId": "00000000000000000000000000000000",
        "isSameRegion": true,
        "status": 1
      }
    ]
  }
}

It shows as a single device, the 6 port switch.

The relay status method, {"method":"passthrough", "params": {"deviceId": "redacted", "requestData": "{\"system\":{\"get_sysinfo\":null},\"emeter\":{\"get_realtime\":null}}" }}, spits back this:

{
  "system": {
    "get_sysinfo": {
      "sw_ver": "1.0.19 Build 200224 Rel.090814",
      "hw_ver": "1.0",
      "model": "HS300(US)",
      "deviceId": "redacted",
      "oemId": "5C9E6254BEBAED63B2B6102966D24C17",
      "hwId": "redacted",
      "rssi": -59,
      "longitude_i": -1224174,
      "latitude_i": 377759,
      "alias": "TP-LINK_Power Strip_2A54",
      "status": "new",
      "mic_type": "IOT.SMARTPLUGSWITCH",
      "feature": "TIM:ENE",
      "mac": "redacted",
      "updating": 0,
      "led_off": 0,
      "children": [
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62200",
          "state": 1,
          "alias": "Plug 1",
          "on_time": 99381,
          "next_action": {
            "type": -1
          }
        },
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62201",
          "state": 1,
          "alias": "Plug 2",
          "on_time": 254051,
          "next_action": {
            "type": -1
          }
        },
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62202",
          "state": 1,
          "alias": "Plug 3",
          "on_time": 254051,
          "next_action": {
            "type": -1
          }
        },
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62203",
          "state": 1,
          "alias": "Plug 4",
          "on_time": 174221,
          "next_action": {
            "type": -1
          }
        },
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62204",
          "state": 1,
          "alias": "Plug 5",
          "on_time": 9718,
          "next_action": {
            "type": 1,
            "schd_sec": 75600,
            "action": 0
          }
        },
        {
          "id": "8006070807D6C11B3D3B5B61455944531BF7C62205",
          "state": 1,
          "alias": "Plug 6",
          "on_time": 13318,
          "next_action": {
            "type": 1,
            "schd_sec": 72000,
            "action": 0
          }
        }
      ],
      "child_num": 6,
      "err_code": 0
    }
  },
  "emeter": {
    "get_realtime": {
      "voltage_mv": 123640,
      "current_ma": 5,
      "power_mw": 83,
      "total_wh": 311,
      "err_code": 0
    }
  }
}

I suspect there's an additional endpoint to provide those id fields in to in order to change the state. I'll need to spin up an emulator to try and pull this out but was hoping someone else might have already done the heavy lifting.

@atkinsj
Copy link

atkinsj commented Jun 7, 2020

Nevermind, simply using the id field of the individual ports in the usual plug on/plug off method works just fine.

@adumont
Copy link
Owner

adumont commented Jun 8, 2020 via email

@atkinsj
Copy link

atkinsj commented Jun 8, 2020

So turns out I spoke too soon. The children device ID's don't work with any of the standard plug commands, issuing the usual {"error_code":-20580,"msg":"Account is not binded to the device"}. I suspect there's a different parameter you need to provide here but I've been unable to determine what it is. The Android application SSL certificate pins and my usual Frida pinning bypass script isn't working, so I haven't been able to MITM the calls to find the underlying params.

@atkinsj
Copy link

atkinsj commented Jun 9, 2020

TLDR:

curl -i -v --request POST "https://use1-wap.tplinkcloud.com/?token=redacted-token HTTP/1.1" --data '{"method":"passthrough", "params": {"deviceId": "redacted device id of the hs300", "requestData": "{\"context\":{\"child_ids\":[\"redacted sub device id of the port\"],\"source\":\"redacted client id\"},\"system\":{\"set_relay_state\":{\"state\":1}}}", }}' --header "Content-Type: application/json"

Alright, I got to the bottom of this. For anyone curious I spun up the Kasa app in an Android emulator and used Frida to hook like below:

Java.perform(function() {
    var iotrequest = Java.use("com.tplinkra.iot.IOTRequest");

    iotrequest.getData.implementation = function() {
	    console.log("getData()")
	    console.log(this.toJSON());

	    return this.getData();
    }
},0);

This prints the JSON body of every request the application sends.

The application is actually sending quite a bit more detail than this API and the documentation on your blog so they could easily detect non-application usage, I don't think it's overly important though.

Here's the full request body for switching off port 6 on my HS300. You don't need this, the minimum TLDR at the top works.

{
  "data": {
    "deviceId": "redacted",
    "requestData": "{\"context\":{\"child_ids\":[\"redacted\"],\"source\":\"redacted\"},\"system\":{\"set_relay_state\":{\"state\":1}}}",
    "appServerUrl": "https://n-use1-wap.tplinkcloud.com",
    "uri": "com.tplinkra.tplink.appserver.impl.PassthroughRequest"
  },
  "iotContext": {
    "deviceContext": {
      "boundEmail": "redacted",
      "deviceAddress": "redacted",
      "deviceAlias": "Light",
      "deviceCategory": "switch",
      "deviceId": "redacted",
      "deviceModel": "HS300(US)",
      "deviceState": {
        "connectionUnavailableCount": 0,
        "deviceTimeDifference": 0,
        "feature": "TIM:ENE",
        "ignoreConnectionUnavailableTracking": false,
        "isUpdating": false,
        "lastConnectionUnavailableTime": 0,
        "latitude": redacted,
        "longitude": redacted,
        "nextAction": {
          "action": 0,
          "id": "redacted",
          "scheduleTime": 75600,
          "type": 1
        },
        "typeUri": "com.tplinkra.iot.devices.smartplug.impl.SmartPlugDeviceState"
      },
      "deviceType": "IOT.SMARTPLUGSWITCH",
      "hardwareId": "redacted",
      "hardwareVersion": "1.0",
      "isBoundToCloud": true,
      "isLocal": false,
      "isRemote": true,
      "isSameRegion": true,
      "model": "HS300",
      "oemId": "5C9E6254BEBAED63B2B6102966D24C17",
      "parentDeviceContext": {
        "appServerUrl": "https://n-use1-wap.tplinkcloud.com",
        "boundEmail": "redacted",
        "cloudStatus": 1,
        "deviceAddress": "redacted",
        "deviceAlias": "TP-LINK_Power Strip_2A54",
        "deviceCategory": "switch",
        "deviceId": "redacted",
        "deviceModel": "HS300(US)",
        "deviceState": {
          "connectionUnavailableCount": 0,
          "ignoreConnectionUnavailableTracking": false,
          "isUpdating": false,
          "lastConnectionUnavailableTime": 0,
          "latitude": redacted,
          "longitude": redacted,
          "typeUri": "com.tplinkra.iot.devices.common.ClassADeviceState"
        },
        "deviceType": "IOT.SMARTPLUGSWITCH",
        "hardwareId": "redacted",
        "hardwareVersion": "1.0",
        "isBoundToCloud": true,
        "isParent": true,
        "isRemote": true,
        "isSameRegion": true,
        "model": "HS300",
        "oemId": "redacted",
        "rssi": -56,
        "softwareVersion": "1.0.19 Build 200224 Rel.090814"
      },
      "rssi": -56,
      "softwareVersion": "1.0.19 Build 200224 Rel.090814"
    },
    "userContext": {
      "accountToken": "redacted",
      "app": {
        "appClientId": "redacted",
        "appType": "Kasa_Android"
      },
      "email": "redacted",
      "terminalId": "redacted"
    }
  },
  "method": "passthrough",
  "requestId": "redacted"
}

@adumont
Copy link
Owner

adumont commented Jun 9, 2020

Thank you very much for the help.

This Frida method might definitely prove useful.
Indeed the Kasa API looks more verbose than it was some years ago, probably due to the fact that they now support a lot more devices. Still, they haven't broken compatibility, so far, with the API.

I'll have a look at what you send when I get some time.

BTW Anyone is welcome to contribute if they feel up to it, don't hesitate to send a Pull Request when you have something working so I can merge it in the NPM module.

@adumont
Copy link
Owner

adumont commented Jun 10, 2020

@atkinsj , in your TL;DR, what is "source":"redacted client id" ? What does it represent here? Is it a required parameter or does it still work if you omit it?

@atkinsj
Copy link

atkinsj commented Jun 10, 2020

It's the application client ID, the uuid4 that you generate. It works when omitted.

@adumont
Copy link
Owner

adumont commented Jun 10, 2020

I don't have a HS300, but I have tried with all the stuff here to come up with something. It might work, or not... If anyone wants to try, it's in the releases: https://github.com/adumont/tplink-cloud-api/releases/tag/v0.6.2-next.0 (2ab601c)

you could try with:

require('dotenv').config()
const { login } = require("tplink-cloud-api");
const uuidV4 = require("uuid/v4");

// define these 3 variables in your .env file
// you can copy .env.sample to .env and edit it
const TPLINK_USER = process.env.TPLINK_USER;
const TPLINK_PASS = process.env.TPLINK_PASS;

// you can optionally define this one, or it will get a value
const TPLINK_TERM = process.env.TPLINK_TERM || uuidV4();

async function main() {
    // log in to cloud, return a connected tplink object
    const tplink = await login(TPLINK_USER, TPLINK_PASS, TPLINK_TERM);

    // get a list of raw json objects (must be invoked before .get* works)
    const dl = await tplink.getDeviceList();
    console.log("DeviceList:", dl);

    let myPlug = tplink.getHS300("my plug");
    let children = await myPlug.getChildren();
    console.log("response",myPlug.findChild("Plug 5"));

    let child5 = myPlug.getChild("Plug 5");
    child5.powerOff();

    console.log("getSysInfo: ", await child5.getSysInfo() );
}
  
main();

don't hesitate to try all the methods of hs300child (see https://github.com/adumont/tplink-cloud-api/blob/v0.6.2-next.0/lib/hs300child.ts) and report if there any failure or success. :)

@adumont
Copy link
Owner

adumont commented Jun 11, 2020

Sorry, just realized I've left some bogus data in the code, so don't bother to test it, it will fail :(. I'll have to remove it, repack and upload a new alpha release later when I find some time.

@adumont
Copy link
Owner

adumont commented Jun 11, 2020

Hopefully I have fixed the package, and you should be able to test if for HS300 support. You can download it from: https://github.com/adumont/tplink-cloud-api/releases/tag/0.6.2-next.2 , and test with code like the one I put in my comment above.

@adumont
Copy link
Owner

adumont commented Jun 19, 2020 via email

@piekstra
Copy link

@adumont I've been using your repo for a project and the child logic you wrote up has been working for me. I haven't tested your code directly, as I've been porting your logic over to Python 3, but it worked in the ported code.

Also, if you could use someone to run some tests on other device types, I have a number of Kasa models I'm using and would gladly help to contribute. (HS103, HS105, HS300)

@adumont
Copy link
Owner

adumont commented Oct 24, 2020

@adumont I've been using your repo for a project and the child logic you wrote up has been working for me. I haven't tested your code directly, as I've been porting your logic over to Python 3, but it worked in the ported code.

Also, if you could use someone to run some tests on other device types, I have a number of Kasa models I'm using and would gladly help to contribute. (HS103, HS105, HS300)

@piekstra Could you try the HS300 support in this version https://github.com/adumont/tplink-cloud-api/releases/tag/0.6.2-next.2 ? (see example code a few comments above, in #27 (comment))

@piekstra
Copy link

@adumont I've been using your repo for a project and the child logic you wrote up has been working for me. I haven't tested your code directly, as I've been porting your logic over to Python 3, but it worked in the ported code.
Also, if you could use someone to run some tests on other device types, I have a number of Kasa models I'm using and would gladly help to contribute. (HS103, HS105, HS300)

@piekstra Could you try the HS300 support in this version https://github.com/adumont/tplink-cloud-api/releases/tag/0.6.2-next.2 ? (see example code a few comments above, in #27 (comment))

@adumont
Worked successfully! I ran with this sample code

async function doThings() {
    const { login } = require("../tplink-cloud-api-0.6.2-next.2");
    const tplink = await login("redacted@email.com", "redacted");

    // get a list of raw json objects (must be invoked before .get* works)
    const dl = await tplink.getDeviceList();
    console.log("DeviceList:", dl);

    let powerStripName = "TP-LINK_Power Strip_0AB1"
    let powerStrip = tplink.getHS300(powerStripName);

    let children = await powerStrip.getChildren();
    console.log("Children of ", powerStripName, " : ", children)

    let child5 = powerStrip.getChild("Test 5");
    console.log("child5 response: ", child5);

    let powerOnResult = await child5.powerOn();
    console.log("powerOnResult: ", powerOnResult)

    let powerOffResult = await child5.powerOff();
    console.log("powerOffResult: ", powerOffResult)

    console.log("getSysInfo: ", await child5.getSysInfo() );
}

doThings();

It successfully read the data and also turned the individual plug on and back off

cleaned_results.txt

@adumont
Copy link
Owner

adumont commented Oct 25, 2020

Great @piekstra. Thank you for taking time to test it. I've published a new updated version (0.8 on npm, including the hs300 support).

e52f75d

@adumont adumont closed this as completed Oct 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants