[update 7 June 2020] Available now on Docker: https://hub.docker.com/repository/docker/georgovassilis/hs100
[update 27 January 2017] TP-LINK HS100 project on github: https://github.com/ggeorgovassilis/linuxscripts
[update December 2020] TL;DR; This is about a shell script which controls the TP-LINK HS100, HS103s, HS110, HS200 and KP105 Wi-Fi smart power plugs. Other models in that product line may also work (the tplink-smarthome-api project uses a similar concept to this script to communicate with a wider range of plugs).
[update January 2021] I realised that when moving from Blogger to WordPress, user comments were lost. I sincerely apologise to everyone who contributed questions and remarks.
I was scurrying down the home automation isle at the local electronics discounter, firm in my determination to make it without distractions to the computer section, when one of those fancy new Wi-Fi power sockets caught my attention. I haven’t quite caught on with the home automation hype yet, but with everything going on I felt left behind and it seemed like a good idea to use the long weekend ahead and try to catch up a bit. And so it came that I left the shop 40€ poorer and with a TP-LINK HS100 Wi-Fi smart plug [1] in the pocket.


Usage is simple: hs100.sh , e.g.
switch plug on:
hs100.sh 192.168.1.20 9999 on
check whether plug is switched on:
Thomas Baust documented the encryption algorithm in a comment (which was unfortunately lost while migrating from Blogger to WordPress):
Hi George, This little c-prgramm can decode the messages – just pipe the raw data to it like: echo AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr|base64 -d|./hs1xdec #include int main(int argc, char *argv[]) { int c=getchar(); int skip=4; int code=0xAB; while(c!=EOF) { if (skip>0) { skip–; c=getchar(); } else { putchar(c^code); code=c; c=getchar(); } } printf(“\n”); }
Hi George, your payload_off sequence is to long, just use AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow== Btw: The “encryption” is just xor every Byte with the previous one. As result you will get a Json message with 5bytes Header and maybe one char padding at the end 😉
Thomas Baust 26 June 2016
Here is an outdated version of the script, head over to github for a recent one.
#!/bin/bash ## # Switch the TP-LINK HS100 wlan smart plug on and off, query for status # Tested with firmware 1.0.8 # # Credits to Thomas Baust for the query/status/emeter commands # # Author George Georgovassilis, https://github.com/ggeorgovassilis/linuxscripts ip=$1 port=$2 cmd=$3 check_binaries() { command -v nc >/dev/null 2>&1 || { echo >&2 "The nc programme for sending data over the network isn't in the path, communication with the plug will fail"; exit 2; } command -v base64 >/dev/null 2>&1 || { echo >&2 "The base64 programme for decoding base64 encoded strings isn't in the path, decoding of payloads will fail"; exit 2; } command -v od >/dev/null 2>&1 || { echo >&2 "The od programme for converting binary data to numbers isn't in the path, the status and emeter commands will fail";} command -v read >/dev/null 2>&1 || { echo >&2 "The read programme for splitting text into tokens isn't in the path, the status and emeter commands will fail";} command -v printf >/dev/null 2>&1 || { echo >&2 "The printf programme for converting numbers into binary isn't in the path, the status and emeter commands will fail";} } # base64 encoded data to send to the plug to switch it on payload_on="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog==" # base64 encoded data to send to the plug to switch it off payload_off="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow==" # base64 encoded data to send to the plug to query it payload_query="AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr" # base64 encoded data to query emeter - hs100 doesn't seem to support this in hardware, but the API seems to be there... payload_emeter="AAAAJNDw0rfav8uu3P7Ev5+92r/LlOaD4o76k/6buYPtmPSYuMXlmA==" usage() { echo Usage: echo $0 ip port on/off/check/status/emeter exit 1 } checkarg() { name="$1" value="$2" if [ -z "$value" ]; then echo "missing argument $name" usage fi } checkargs() { checkarg "ip" $ip checkarg "port" $port checkarg "command" $cmd } sendtoplug() { ip="$1" port="$2" payload="$3" echo -n "$payload" | base64 -d | nc -v $ip $port || echo couldn''t connect to $ip:$port, nc failed with exit code $? } check(){ output=`sendtoplug $ip $port "$payload_query" | base64` if [[ $output == AAACJ* ]] ; then echo OFF fi if [[ $output == AAACK* ]] ; then echo ON fi } status(){ payload="$1" code=171 offset=4 input_num=`sendtoplug $ip $port "$payload" | od --skip-bytes=$offset --address-radix=n -t u1 --width=9999` IFS=' ' read -r -a array <<< "$input_num" for element in "${array[@]}" do output=$(( $element ^ $code )) printf "\x$(printf %x $output)" code=$element done } ## # Main programme ## check_binaries checkargs case "$cmd" in on) sendtoplug $ip $port "$payload_on" > /dev/null ;; off) sendtoplug $ip $port "$payload_off" > /dev/null ;; check) check ;; status) status "$payload_query" ;; emeter) status "$payload_emeter" ;; *) usage ;; esac exit 0
Resources
https://github.com/ggeorgovassilis/linuxscripts/tree/master/tp-link-hs100-smartplug
hs100 on Docker hub
https://hub.docker.com/repository/docker/georgovassilis/hs100
Hello, works great with HS100 on Raspbian, works also on my Arch Linux after installing gnu-netcat, but script is hanging for long time without error, may be missing some dependences?
LikeLike
Well… it either “works great” or it “hangs” but not both 🙂 Can you open a ticket? https://github.com/ggeorgovassilis/linuxscripts/issues
LikeLike
HI, I’m trying to make this work, so far unsuccessfully. I’m not running a linux script. I’m sending the payload using another program. I tried converting the payload to hex using an online base64 decode tool. A couple of things. The hex output is not 55 bytes, but 46 bytes. Also, the payload on and payload off differ at bytes 43, 44 45 and 46.
Do you have any advice for what to try? Do you have the actual payload going to the unit in hex, as I suspect its the base64 decode I’m getting wrong?
LikeLike
A user emailed me last year with a similar issue. I think something gets lost during copy-pasting in the browser with online decoders. You probably should try a base64 decoder program for your operating system. The on and off payloads differing sounds plausible. The binary payload must be sent to the plug; scripts can’t store binary data, so it is written there with the base64 encoding and later translated to binary with a base64 decoder. If you can run docker, you always can try the dockerised version of the script.
LikeLike
Thanks for your efforts figuring out how to communicate with these smart plugs! I’ve just acquired several HS103s and your script works wonderfully with them. However, one thing I’ve noticed is that it seems like the script takes a long time to finish running, upwards of 20 – 30 seconds. Is this the expected behavior for these devices and is there anything I can do to speed things up?
LikeLike
Thanks for letting me know about the HS103s compatibility. If you give the script a name rather than an IP it could be a name resolution issue (eg. DNS trying first to resolve IPv6 before falling back to IPv4). If you add “echo linenumber” statements into key locations at the script and show me the output maybe I can figure it out?
LikeLike
I’ve had a chance to do a little more troubleshooting and figured out the issue. For some reason the smart plug is holding the connection open for quite awhile after a command is sent, so the script has to wait for it to time out, and then parses/displays the output.
There are two ways to address the issue by varying the invocation of the netcat command in your send_to_plug() function. The first is by adding the “q” parameter and the second by adding the “w” parameter. Both of these parameters control timeouts in netcat. The “q” parameter tells netcat to disconnect X seconds after EOF on STDIN, and the “w” parameter tells netcat to disconnect after the remote end has been idle for X seconds.
In experimenting with the script, the “q” parameter results in quicker execution, but for very short timeout values may result in netcat disconnecting before it receives the output from the smart plug. This is really only an issue for commands like “status” that rely on receiving data back. The “w” parameter is safer since it waits until data has been received, but seems to add a couple extra seconds to the overall execution time regardless of the timeout used.
Here’s my modification to the netcat invocation in line 64 in the send_to_plug() function:
nc -q 1 -v $ip $port
That gives me a near instant execution, but I would suggest as a generally “safe” modification to the script to use something like -w 5
LikeLike
Thanks for your work on this. The netcat implementation on arch linux doesn’t have a nice timeout that works (as per Jordan W.’s comment) so I rewrote the bare functionality in python. Working with a hs110 (including the emeter functionality – which is what I bought the plug for).
https://github.com/Nealefelaen/hs100
LikeLike
Still works great on
{“system”:{“get_sysinfo”:{“sw_ver”:”1.1.5 Build 200828 Rel.081157″,”hw_ver”:”4.0″,”model”:”HS100(EU)”
Which i just bought from amazon Germany. I’ll now make an android termux script (with termux api app) to turn off the plug and stop charging my phone at 85% battery when i leave it plugged overnight. Thank you!
LikeLike
That is a great use case!
LikeLike
Hi, this works great – thank you for sharing…do you know if it will also work on the newer KP105 plugs….cant see any reason why it shouldnt
LikeLike
Sorry David, I haven’t tried the KP105.
LikeLike
Hi again,
I can confirm that these work the same as the originals 🙂 🙂
________________________________
LikeLiked by 1 person
yes, these seem to work the same as the original plugs
LikeLike
I can confirm that the same shell script, and Nealefelaen’s python script above, both can control a Kasa HS210 3 way switch. I haven’t hooked it up in a 3 way light circuit yet but the switch relay sounds as though successive ‘off’ commands toggle the state, as evidenced by a slightly different relay sound. The ‘on’ command makes no relay click noise.
My end goal is to implement a time delay turn off function for this outdoor 3 way switched light.
LikeLiked by 1 person
Also of interest, Kasa (TP-Link) is/has removed this port 9999 interface on new software updates to the plugs. Best to block Internet access if you have the device active on the Kasa App. You can ‘request a beta firmware’ with the old interface restored.
https://www.home-assistant.io/blog/2020/11/23/tplink-local-access/
LikeLike
Thanks for bringing this up. When analysing the traffic between app and the internet we found sensitive information like location being transmitted – the only real security hole in this scenario, imho, is the Kaza app.
LikeLike