1
0
mirror of https://github.com/taigrr/tplinkController synced 2025-01-18 04:43:13 -08:00

Compare commits

...

26 Commits
v1.0 ... master

Author SHA1 Message Date
Jason Benaim
68931c71bd Add missing word in README. 2019-07-27 22:03:17 -07:00
Jason Benaim
60cc5c8bb5 Remove unused JSON stuff. 2019-07-19 12:46:33 -07:00
Jason Benaim
b42c1e293c Revert "Merge pull request #1 from mikeryan/lookup_hostname"
Broke builds on some systems.
2019-07-19 12:45:46 -07:00
Jason Benaim
0f1f472cf0 Revert "Makefile: change C standard to Gnu99 from C99. This fixes issue #2."
Didn't fix the problem on all systems, so reverting...
2019-07-19 12:45:23 -07:00
Jason Benaim
de6ac2b222 Makefile: change C standard to Gnu99 from C99. This fixes issue #2. 2019-07-19 12:31:45 -07:00
Jason Benaim
fbe8da7a4b
Merge pull request #1 from mikeryan/lookup_hostname
resolve hostname using getaddrinfo
2019-05-25 16:11:18 -07:00
Mike Ryan
b24673100f resolve hostname using getaddrinfo 2019-05-25 14:43:19 -07:00
Jason Benaim
4ca74c5eba Add JSON builder/parser. 2019-03-10 18:34:45 -07:00
Jason Benaim
c57babc9b1 C99 -> ANSI for comms.c, handlers.c 2019-03-10 18:32:57 -07:00
Jason Benaim
80581e5c9c Style fussing 2019-03-03 23:46:23 -08:00
Jason Benaim
4be0d86405 free() after calloc() 2019-03-03 23:35:40 -08:00
Jason Benaim
5d16c96ef0 Support longer responses from device (needed for HS300) 2019-03-03 21:49:32 -08:00
Jason Benaim
94d4f10d87 Update copyright year to current 2019-03-03 21:42:34 -08:00
Jason Benaim
4bbecdaea0 small formatting changes 2018-11-24 21:17:15 -08:00
Jason Benaim
f870b6458d .gitignore: ignore Kate backup files 2018-11-24 21:10:58 -08:00
Jason Benaim
e24f5d53f1 Remove unnecessary includes 2018-11-24 21:04:42 -08:00
Jason Benaim
89f819707d Move handlers into their own file 2018-11-24 21:03:58 -08:00
Jason Benaim
2e1fe081a1
Minor update in README.md 2018-11-24 07:10:57 -08:00
Jason Benaim
2f0beec450 Bump version to 1.1 2018-11-24 05:29:50 -08:00
Jason Benaim
b66401d5a1 Add ability to do first-time plug setup 2018-11-24 05:29:26 -08:00
Jason Benaim
f01121c37c Fix: using -Werror for non-debug builds 2018-11-24 03:19:58 -08:00
Jason Benaim
20a06eec3e Add scan command for wifi AP scanning 2018-11-24 03:15:38 -08:00
Jason Benaim
8ff3ba7abf Better usage info 2018-11-24 03:15:11 -08:00
Jason Benaim
5ccb2fd24d Add reboot and reset commands 2018-11-24 02:18:02 -08:00
Jason Benaim
629708555c Fix: crashes due to malformed replies 2018-11-24 01:59:35 -08:00
Jason Benaim
909e5c403e Fix: didn't always free buffer 2018-11-24 01:03:22 -08:00
7 changed files with 290 additions and 78 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
hs100
*.o
*.swp
*~

View File

@ -1,6 +1,11 @@
target ?= hs100
objects := $(patsubst %.c,%.o,$(wildcard *.c))
CFLAGS=-Wall -Werror -std=c99 -ggdb -Os
LDFLAGS=-lm
CFLAGS=-std=c99 -Os
ifdef DEBUG
CFLAGS+=-Wall -Werror -ggdb
endif
.PHONY: all
all: $(target)

View File

@ -1,18 +1,64 @@
# hs100
A small program for flipping TP-Link HS100/HS105/HS110 wi-fi smart plugs on
and off.
A tool for using TP-Link HS100/HS105/HS110 wi-fi smart plugs. You can turn
them on and off, reboot them, and so on. You can even set them up without
using TP-Link's app (see Initial Setup).
Tested to work on Linux, OSX, IRIX, and Windows under WSL.
Loosely based on [pyHS100](https://github.com/GadgetReactor/pyHS100) and
[research from softScheck](https://www.softscheck.com/en/reverse-engineering-tp-link-hs110/).
Tested to work on Linux, OSX, and IRIX.
## Usage
```hs100 <ip of plug> <json command blob>```
As a convenience, you can supply the words 'on' or 'off' in place of a json
command blob.
`hs100 <ip> <command>`
Commands:
- `associate <ssid> <key> <key_type>`: set wifi AP to connect to. get your
key\_type by doing a scan
- `factory-reset`: reset the plug to factory settings
- `off`: turn the power off
- `on`: turn the power on
- `reboot`: reboot the plug
- `scan`: scan for nearby wifi APs (probably only 2.4 GHz ones)
- `set_server <url>`: set cloud server to \<url\> instead of TP-Link's
- Alternatively, you can supply a JSON string to be sent directly to the
device. Note that the JSON string must be quoted, like so:
`hs100 <ip> '{"system":{"set_relay_state":{"state":1}}}'`
## Initial Setup
According to TP-Link, initial setup of the plugs is performed by installing
their "Kasa" app on your smartphone (free account required), and using its
setup tool. This sucks and I do not recommend it. Instead, follow these
alternative instructions.
You want to get the plug into the "blinking amber and blue" state, in which
it will spin up its own AP and await commands. If you have a brand new plug,
then it should do this automatically. Otherwise, hold down one of the buttons
(depending on your model) for about 5 seconds, until its light blinks amber
and blue.
You should see a wifi AP called "TP-Link\_Smart Plug\_XXXX" or similar.
Connect to this AP. You will be given an IP of 192.168.0.100, with the plug
at 192.168.0.1.
Issue the following commands to the plug:
- Factory reset the plug to get rid of any settings from a previous owner:
`hs100 192.168.0.1 factory-reset`. You will be disconnected from its wifi AP.
Once the factory reset is done (usually a few seconds), reconnect to the
plug's AP.
- Disable cloud nonsense by setting a bogus server URL: `hs100 192.168.0.1 set_server localhost`
- Scan for your wifi AP using `hs100 192.168.0.1 scan`. Find your AP in the
list and note its `key_type`; you will need this to associate.
- Associate with your AP using `hs100 192.168.0.1 associate <ssid> <password> <key_type>`
. Your key\_type is a number that indicates the kind of wifi security that
your AP is using. You can find it by doing a wifi scan (see previous step).
If the light turns solid amber, then it was unable to associate-- factory
reset the plug and try again. Otherwise, the light on your plug will change
first to blinking blue, then to solid blue indicating that it has successfully
connected to your AP.
## Todo

117
comms.c
View File

@ -1,25 +1,28 @@
#include <stddef.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "comms.h"
bool hs100_encrypt(uint8_t *d, uint8_t *s, size_t len)
{
if(d == NULL)
uint8_t key, temp;
size_t i;
if (d == NULL)
return false;
if(s == NULL)
if (s == NULL)
return false;
if(len == 0)
if (len == 0)
return false;
uint8_t key = 0xab;
for(size_t i=0; i<len; i++) {
uint8_t temp = key ^ s[i];
key = 0xab;
for (i = 0; i < len; i++) {
temp = key ^ s[i];
key = temp;
d[i] = temp;
}
@ -28,16 +31,19 @@ bool hs100_encrypt(uint8_t *d, uint8_t *s, size_t len)
bool hs100_decrypt(uint8_t *d, uint8_t *s, size_t len)
{
if(d == NULL)
uint8_t key, temp;
size_t i;
if (d == NULL)
return false;
if(s == NULL)
if (s == NULL)
return false;
if(len == 0)
if (len == 0)
return false;
uint8_t key = 0xab;
for(size_t i=0; i<len; i++) {
uint8_t temp = key ^ s[i];
key = 0xab;
for (i = 0; i < len; i++) {
temp = key ^ s[i];
key = s[i];
d[i] = temp;
}
@ -46,17 +52,23 @@ bool hs100_decrypt(uint8_t *d, uint8_t *s, size_t len)
uint8_t *hs100_encode(size_t *outlen, char *srcmsg)
{
if(srcmsg == NULL) return NULL;
size_t srcmsg_len;
uint8_t *d;
uint32_t temp;
size_t srcmsg_len = strlen(srcmsg);
if (srcmsg == NULL)
return NULL;
srcmsg_len = strlen(srcmsg);
*outlen = srcmsg_len + 4;
uint8_t *d = calloc(1, *outlen);
if(d == NULL) return NULL;
if(!hs100_encrypt(d+4, (uint8_t *)srcmsg, srcmsg_len)) {
d = calloc(1, *outlen);
if (d == NULL)
return NULL;
if (!hs100_encrypt(d + 4, (uint8_t *) srcmsg, srcmsg_len)) {
free(d);
return NULL;
}
uint32_t temp = htonl(srcmsg_len);
temp = htonl(srcmsg_len);
memcpy(d, &temp, 4);
return d;
@ -64,16 +76,24 @@ uint8_t *hs100_encode(size_t *outlen, char *srcmsg)
char *hs100_decode(uint8_t *s, size_t s_len)
{
if(s == NULL) return NULL;
if(s_len <= 4) return NULL;
uint32_t in_s_len;
char *outbuf;
if (s == NULL)
return NULL;
if (s_len <= 4)
return NULL;
memcpy(&in_s_len, s, 4);
in_s_len = ntohl(in_s_len);
if ((s_len - 4) < in_s_len) {
/* packet was cut short- adjust in_s_len */
in_s_len = s_len - 4;
}
char *outbuf = calloc(1,in_s_len+1);
outbuf = calloc(1, in_s_len + 1);
if(!hs100_decrypt((uint8_t*)outbuf, s+4, in_s_len)) {
if (!hs100_decrypt((uint8_t *) outbuf, s + 4, in_s_len)) {
free(outbuf);
return NULL;
}
@ -84,35 +104,44 @@ char *hs100_decode(uint8_t *s, size_t s_len)
char *hs100_send(char *servaddr, char *msg)
{
size_t s_len;
uint8_t *s = hs100_encode(&s_len, msg);
if(s == NULL)
int sock;
uint8_t *s, *recvbuf;
struct sockaddr_in address;
uint32_t msglen;
size_t recvsize;
char *recvmsg;
s = hs100_encode(&s_len, msg);
if (s == NULL)
return NULL;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return NULL;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0) return NULL;
struct sockaddr_in address;
memset(&address, '0', sizeof(struct sockaddr_in));
address.sin_family = AF_INET;
address.sin_port = htons(9999);
if(inet_pton(AF_INET, servaddr, &address.sin_addr)<=0)
if (inet_pton(AF_INET, servaddr, &address.sin_addr) <= 0)
return NULL;
if(connect(sock, (struct sockaddr *)&address,
if (connect(sock, (struct sockaddr *)&address,
sizeof(struct sockaddr_in)) < 0)
return NULL;
send(sock, s, s_len, 0);
uint8_t recvbuf[1024];
recv(sock, recvbuf, 1023, 0);
char *recvmsg = hs100_decode(recvbuf, 1023);
close(sock);
if(recvmsg == NULL) {
free(s);
free(s);
recvsize = recv(sock, &msglen, sizeof(msglen), MSG_PEEK);
if (recvsize != sizeof(msglen)) {
return NULL;
}
msglen = ntohl(msglen) + 4;
recvbuf = calloc(1, (size_t) msglen);
recvsize = recv(sock, recvbuf, msglen, MSG_WAITALL);
close(sock);
recvmsg = hs100_decode(recvbuf, msglen);
free(recvbuf);
return recvmsg;
}

65
handlers.c Normal file
View File

@ -0,0 +1,65 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "comms.h"
char *handler_associate(int argc, char *argv[])
{
const char *template =
"{\"netif\":{\"set_stainfo\":{\"ssid\":\"%s\",\"password\":"
"\"%s\",\"key_type\":%d}}}";
char *plug_addr = argv[1];
char *ssid = argv[3];
char *password = argv[4];
char *key_type = argv[5];
char *endptr, *msg, *response;
int key_type_num;
size_t len;
if (argc < 6) {
fprintf(stderr, "not enough arguments\n");
exit(1);
}
errno = 0;
key_type_num = (int)strtol(key_type, &endptr, 10);
if (errno || endptr == key_type) {
fprintf(stderr, "invalid key type: %s\n", key_type);
exit(1);
}
len = snprintf(NULL, 0, template, ssid, password,
key_type_num);
len++; /* snprintf does not count the null terminator */
msg = calloc(1, len);
snprintf(msg, len, template, ssid, password, key_type_num);
response = hs100_send(plug_addr, msg);
return response;
}
char *handler_set_server(int argc, char *argv[])
{
const char *template =
"{\"cnCloud\":{\"set_server_url\":{\"server\":\"%s\"}}}";
char *plug_addr = argv[1];
char *server = argv[3];
size_t len;
char *msg, *response;
if (argc < 4) {
fprintf(stderr, "not enough arguments\n");
exit(1);
}
len = snprintf(NULL, 0, template, server);
len++; /* snprintf does not count the null terminator */
msg = calloc(1, len);
snprintf(msg, len, template, server);
response = hs100_send(plug_addr, msg);
return response;
}

111
hs100.c
View File

@ -1,59 +1,118 @@
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "version.h"
#include "comms.h"
const char usage[] =
"usage: hs100 ip command-or-json\n"
"where 'ip' is an IP address (ex. 192.168.1.1)\n"
"and 'command' is one of the words 'on', 'off', or a blob of json\n";
// handlers for more complicated commands
extern char *handler_associate(int argc, char *argv[]);
extern char *handler_set_server(int argc, char *argv[]);
struct cmd_alias_s {
char *alias;
struct cmd_s {
char *command;
char *help;
char *json;
char *(*handler) (int argc, char *argv[]);
int end;
};
struct cmd_alias_s cmd_aliases[] = {
struct cmd_s cmds[] = {
{
.alias = "off",
.command = "{\"system\":{\"set_relay_state\":{\"state\":0}}}",
.command = "associate",
.help = "associate <ssid> <key> <key_type>\n"
"\t\t\tset wifi AP to connect to",
.handler = handler_associate,
},
{
.alias = "on",
.command = "{\"system\":{\"set_relay_state\":{\"state\":1}}}",
.command = "factory-reset",
.help = "factory-reset\treset the plug to factory settings",
.json = "{\"system\":{\"reset\":{\"delay\":0}}}",
},
{
.command = "info",
.help = "info\t\tget device info",
.json = "{\"system\":{\"get_sysinfo\":{}}}",
},
{
.command = "off",
.help = "off\t\tturn the plug on",
.json = "{\"system\":{\"set_relay_state\":{\"state\":0}}}",
},
{
.command = "on",
.help = "on\t\tturn the plug off",
.json = "{\"system\":{\"set_relay_state\":{\"state\":1}}}",
},
{
.command = "reboot",
.help = "reboot\t\treboot the plug",
.json = "{\"system\":{\"reboot\":{\"delay\":0}}}",
},
{
.command = "scan",
.help = "scan\t\tscan for nearby wifi APs (probably only 2.4"
" GHz ones)",
.json = "{\"netif\":{\"get_scaninfo\":{\"refresh\":1}}}",
},
{
.command = "set_server",
.help = "set_server <url>\n"
"\t\t\tset cloud server to <url> instead of tplink's",
.handler = handler_set_server,
},
{
.end = 1,
},
};
char *get_cmd(char *needle)
struct cmd_s *get_cmd_from_name(char *needle)
{
int cmds_index = 0;
while(!cmd_aliases[cmds_index].end)
{
if(!strcmp(cmd_aliases[cmds_index].alias, needle))
return cmd_aliases[cmds_index].command;
while (!cmds[cmds_index].end) {
if (!strcmp(cmds[cmds_index].command, needle))
return &cmds[cmds_index];
cmds_index++;
}
return needle;
return NULL;
}
void print_usage()
{
fprintf(stderr, "hs100 version " VERSION_STRING
", Copyright (C) 2018-2019 Jason Benaim.\n"
"A tool for using certain wifi smart plugs.\n\n"
"usage: hs100 <ip> <command>\n\n"
"Commands:\n"
);
int cmds_index = 0;
while (!cmds[cmds_index].end) {
fprintf(stderr, "\t%s\n\n", cmds[cmds_index].help);
cmds_index++;
}
fprintf(stderr, "Report bugs to https://github.com/jkbenaim/hs100\n");
}
int main(int argc, char *argv[])
{
if(argc != 3) {
fprintf(stderr, "%s", usage);
if (argc < 3) {
print_usage();
return 1;
}
char *plug_addr = argv[1];
char *cmd = argv[2];
char *cmd_string = argv[2];
char *response = NULL;
cmd = get_cmd(cmd);
char *response = hs100_send(plug_addr, cmd);
if(response == NULL) {
struct cmd_s *cmd = get_cmd_from_name(cmd_string);
if (cmd != NULL) {
if (cmd->handler != NULL)
response = cmd->handler(argc, argv);
else if (cmd->json != NULL)
response = hs100_send(plug_addr, cmd->json);
} else {
// command not recognized, so send it to the plug raw
response = hs100_send(plug_addr, cmd_string);
}
if (response == NULL) {
fprintf(stderr, "failed to send command\n");
return 1;
}

7
version.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef __VERSION_H__
#define __VERSION_H__
#define VERSION_STRING "1.1"
#define VERSION_NUMBER (101)
#endif