Controlling TP-Link Kasa Smart Switches with PowerShell


Recently I installed a few TP-Link smart light switches in my house to make turning certain lights on and off easier.  I've been using them for about a month and they integrate perfectly with my Google Home Mini too.  There was one thing missing though - being able to turn the outside lights on and off at sunset and sunrise.

At first I went looking for a scheduling functionality in the Kasa app but found out that in order to get scheduling functionality you need to purchase a smart home router to enable the functionality. Next I tried setting up the popular IFTT app on my phone and it worked - sort of.  I was constantly having trouble running the routine on a regular basis.  Some days the lights would never turn on, and other days they'd never turn off.

So I set out to find a better solution. I've recently been working with REST API's at work and I got to wondering if there was an API out there to work with Kasa devices. After some googling I came across a couple projects that showed that there was.

I'd like to take a moment to give credit to a couple projects that made this crazy idea possible.  Without them I'd have no idea how to format the POST body's that we send to the API.  They are the real heroes in this story.

Reverse Engineering the TP-Link HS110
by Lubomir Stroetmann, Consultant and Tobias Esser, Consultant
These two guys put a lot of work into reverse engineering TP-Link smart devices and have mapped out a whole list of available methods and properties on the API. The list and some Python code for controlling devices is available in their Github Project

ITNerd.Space
Aleandre Dumont
Alexandre has a whole series of posts on his blog about working with Kasa devices using Node.js. He has a Github repo with code the code. You can also download the utilities using the Node Package Manager (NPM).

The Code 

A Github Repo with the code is available here
 
A quick disclaimer: I'm using HS200 switches and I think that these methods will work with other devices but I haven't tested it. If you try this out, let me know how it goes. Now, on to the code!

First, we need to authenticate to the TP-Link API and retrieve a token. This token is what we use to tell the API to trust us when we send the commands to actually turn the devices on and off. To do this, we need to send our Kasa username and password, along with a UUID (generate one here) to the API.  It will return a JSON response containing the token.

# This script is used to request a token from the TP-Link Kasa API
# Written by Jake Weaver
# January 2019

$params = "{
    'appType':'Kasa_Android',
    'cloudUserName':'your@email.com',
    'cloudPassword':'yourpassword',
    'terminalUUID':'your uuid',
    }"

$body = "{
    'method':'login',
    'params':$params
   }"

$response = Invoke-RestMethod "https://wap.tplinkcloud.com" -Method POST -Body $body -ContentType application/json

$data = ($response.Result)

$data.token

This token can be used for multiple requests but will eventually time out and you'll have to request a new one.  As you'll see I just get a new token for each request.

Now we can query the devices on our Kasa account to get the deviceIDs that we want to work with.

# This script gets a list of devices associated with a TP-Link Kasa account
# Written by Jake Weaver
# January 2019

# Get the token
$token = .\Get-KasaToken.ps1

$body = "{'method':'getDeviceList'}"

$response = Invoke-RestMethod "https://wap.tplinkcloud.com?token=$token" -Method POST -Body $body -ContentType application/json

$devices = $response.Result

$devices.deviceList

This will output some JSON with all the devices associated with your account and some details about them.  We're specifically looking for the deviceID. Once you have your deviceID, you can continue on to the next step.

Now we can get some fun information about the switch like the wireless signal strength, the status of the switch, and the location in latitude/longitude coordinates. I assume TP-Link is using a geo IP database to get this based on the public IP address that the switch uses to talk to the API.
This script takes a deviceID as an input.  You can pass multiple deviceIDs separated by commas.

# This script gets the state of a TP-Link Kasa Device
# Written by Jake Weaver
# January 2019

param (
    # The device(s) to control
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string[]] $DeviceID
)

# Get the token
$token = .\Get-KasaToken.ps1

foreach ($id in $DeviceID){

    #Build the body in JSON
    $body = "{
        `"method`":`"passthrough`",
        `"params`":{
            `"deviceId`":`"$id`",
            `"requestData`":`"{\`"system\`":{\`"get_sysinfo\`":null},\`"emeter\`":{\`"get_realtime\`":null}}`"
        }
    }"

    # Make the call to the API
    $response = Invoke-RestMethod  "https://wap.tplinkcloud.com?token=$token" -Method POST -Body $body -ContentType application/json

    $data = $response.result.responseData | ConvertFrom-Json
    $system = ConvertTo-Json $data.system

    $temp = $system | ConvertFrom-Json
    $device = $temp.get_sysinfo
    
    $device
}

Now let's set the switch status.  This script takes a list of deviceIDs like the last one, and the state you'd like to set the switch to.

# This script sets the state of TP-Link Kasa Smart Switches
# Written by Jake Weaver
# January 2019

param (
    # The device(s) to control
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string[]] $DeviceID,

    # The state to set the switches to
    [Parameter(Mandatory = $true)]
    [ValidateSet("On","Off")]
    [string] $State
)

# Get the token
$token = .\Get-KasaToken.ps1

# Convert the State string to numerical values
$numericalState = switch ($State) {
    "On" { 1 }
    "Off" { 0 }
}

# output variable
$output =@()

# Loop through the devices that were passed
foreach ($id in $DeviceID) {

    #Build the body in JSON
    $body = "{
                `"method`":`"passthrough`",
                `"params`":{
                    `"deviceId`":`"$id`",
                    `"requestData`":`"{\`"system\`":{\`"set_relay_state\`":{\`"state\`":$numericalState}}}`"
                }
            }"

    # Make the call to the API
    $response = Invoke-RestMethod  "https://use1-wap.tplinkcloud.com?token=$token" -Method POST -Body $body -ContentType application/json

    $output += [PSCustomObject]@{
        DeviceID = $id
        ErrorCode = $response.error_code
    }
}

$output

And that's it! You can now control your TP-Link Kasa switches using PowerShell.

If you look at the Github repo from Lubomir and Tobias that I linked above you can find additional methods to call such as checking the amount of power used by a smart outlet. They've reverse engineered an entire list of functions.

Thanks for reading!

Comments

Popular posts from this blog

Google Domains Dynamic DNS Updates Using Powershell

An introduction