TP-Link HS105 with Java API
HS-105 Initial Setup
Connecting to WiFi using the Kasa for Mobile App
First, get the free Kasa app (iOS | Android) (in the Apple App Store on my iPhone, searched for "tp-link kasa" and found it).I disconnected from my 5 GHz network and connected to the 'regular' (2.4 GHz) WiFi network, as apparently the TP-Link devices only speak 2.4 GHz.
Clicked the '+' (upper right corner) to add a device, and scrolled down to find the Smart Plug Mini.
My HS105 was already plugged in and powered on (and running a RetroPie ... ;) ) ... Once it started blinking orange/blue, went into Settings on the iPhone to connect to its WiFi network (TP-Link_Smart Plug_DEB5).
Back to the Kasa app.
Gave it a name (RPi Plug).
Gave it the credentials for my WiFi network.
Updated the firmware when prompted (took about 60 seconds).
Determine the Network Address
From a UNIX (Mac OS X) machine, fired up Terminal.app and ran 'ping' on my broadcast address:
$ ping -c 4 192.168.1.255
$ arp -a | grep -i b0
... 192.168.1.118 ...
Ran nmap to ensure port 9999 is being used:
$ nmap -Pn 192.168.1.118
Starting Nmap 7.60 ( https://nmap.org ) at 2018-02-17 08:23 PST
Nmap scan report for HS105.local (192.168.1.118)
Host is up (0.0045s latency).
Not shown: 999 filtered ports
PORT STATE SERVICE
9999/tcp open abyss
Nmap done: 1 IP address (1 host up) scanned in 49.71 seconds
Java API
Created a new Java project in Eclipse, added the bundled gson-2.6.2.jar to its build path ("Build Path" / "Add External Archives..."). Dragged and dropped the 'net' directory into 'src' which auto-created the net.insxnity.hs100 package. My first test code seemed to fail:
HS100 plug = new HS100("192.168.1.118");
System.out.println("Plug present? " + plug.isPresent());
Always returned false. A bit of digging showed .isPresent() just returned the result of InetAddress.isReachable(), so I rolled my own:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import net.insxnity.hs100.HS100;
public class Main {
public static void main(String[] args) throws IOException {
HS100 plug = new HS100("192.168.1.118");
System.out.println("Plug present? " + isReachable( plug.getIp(), plug.getPort(), 1500));
}
static boolean isReachable(String addr, int port, int timeout) {
try {
Socket sock = new Socket();
sock.connect(new InetSocketAddress(addr, port), timeout);
return true;
} catch (IOException ex) {
return false;
}
}
That worked. Folding that into hs100.java:
/**
* return if the Plug is valid and present
*
* @return present
*/
public boolean isPresent() {
try {
Socket sock = new Socket();
sock.connect(new InetSocketAddress(getIp(), getPort()), 1500);
return true;
} catch (IOException ex) {
return false;
}
// try {
//
// InetAddress ip = InetAddress.getByName(getIp());
// return ip.isReachable(1500);
// } catch (IOException ex) {}
// return false;
}
That worked, too. Next step ... Watching with debugging as it seemed to be hanging on .isOn() - it wasn't hanging, but it was taking forever (45+ seconds):
public static void main(String[] args) throws IOException {
HS100 plug = new HS100("192.168.1.118");
System.out.println("Plug present? " + plug.isPresent() );
System.out.println("Plug on? " + plug.isOn() );
}
Incidentally, here's the payload being returned from the HS105:
{"system":{"get_sysinfo":{"sw_ver":"1.5.1 Build 171109 Rel.165500","hw_ver":"1.0","type":"IOT.SMARTPLUGSWITCH","model":"HS105(US)","mac":"B0:4E:26:1F:DE:B5","dev_name":"Smart Wi-Fi Plug Mini","alias":"RPi Plug","relay_state":1,"on_time":2379,"active_mode":"none","feature":"TIM","updating":0,"icon_hash":"","rssi":-57,"led_off":0,"longitude_i":0,"latitude_i":0,"hwId":"E5D7E6089B060EF662783C23AE110522","fwId":"00000000000000000000000000000000","deviceId":"8006A2A8B0D68C671EEDE1DB05B6DEEB1967F775","oemId":"003E098AF0D44D4BAB796B3F6A7A830E","err_code":0}}}
(Noticed in my poking that sendCommand is hard-coded to open a socket to port 9999, changed that to use .getPort() )
My modifications to hs100.java (the full, modified, file I put in pastebin):
protected String sendCommand(String command) throws IOException {
// Socket socket = new Socket(getIp(), 9999); // flying-geek
Socket socket = new Socket(getIp(), getPort() ); // flying-geek
socket.setSoTimeout(1000); // flying-geek
...
private String decrypt(InputStream inputStream) throws IOException {
int in;
int key = 0x2B;
int nextKey;
StringBuilder sb = new StringBuilder();
try { // flying-geek
while((in = inputStream.read()) != -1) {
nextKey = in;
in = in ^ key;
key = nextKey;
sb.append((char) in);
}
} catch(SocketTimeoutException e) {} // flying-geek
return "{" + sb.toString().substring(5);
}
Now, instead of hanging (blocking) at the end of the read() loop, if no further data has been read by the end of the timeout period (set at a relatively low 1 second here), it wraps things up on its own. For some reason .isOff() wasn't in the .java file I had, so I added a simple one (watched a couple of debug runs to determine the only change between the plug being switched on, and off, was relay_state set to 1 and 0, respectively, so reproducing any query, etc., from .isOn() made no sense):
/**
* check if the plug is off (added by flying-geek)
*
* @return STATE_ON oder STATE_OFF
*/
public boolean isOff() throws IOException {
return !isOn();
}
With the API source code sufficiently tweaked, the following proof of concept worked like a charm:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import net.insxnity.hs100.HS100;
public class Main {
public static void main(String[] args) throws IOException {
HS100 plug = new HS100("192.168.1.118");
System.out.println("Plug address " + plug.getIp() + ":" + plug.getPort());
System.out.println("Plug present? " + plug.isPresent() );
for(int i = 0; i < 3; i++) {
System.out.println("Iteration " + i);
System.out.println("Plug on? " + plug.isOn() );
System.out.println("Plug off? " + plug.isOff() );
if( plug.isOn() ) {
System.out.println("... switching off:");
plug.switchOff();
} else {
System.out.println("... switching on:");
plug.switchOn();
}
}
}
}
Plug address 192.168.1.118:9999
Plug present? true
Iteration 0
Plug on? false
Plug off? true
... switching on:
Iteration 1
Plug on? true
Plug off? false
... switching off:
Iteration 2
Plug on? false
Plug off? true
... switching on:
Comments
Post a Comment