Skip to content

Instantly share code, notes, and snippets.

@agent4788
Last active November 17, 2023 13:34
Show Gist options
  • Save agent4788/81beb25cdcdbf7e9371361ca87d3b04a to your computer and use it in GitHub Desktop.
Save agent4788/81beb25cdcdbf7e9371361ca87d3b04a to your computer and use it in GitHub Desktop.
Mit dieser kleinen API kann man die TP-Link HS100 und HS110 Wlan Steckdosen direkt aus Java ansteuern.
package de.test;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
/**
* TP-Link HS100
*
* @author Oliver Kleditzsch
* @copyright Copyright (c) 2016, Oliver Kleditzsch
*/
public class HS100 {
public static final String COMMAND_SWITCH_ON = "{\"system\":{\"set_relay_state\":{\"state\":1}}}}";
public static final String COMMAND_SWITCH_OFF = "{\"system\":{\"set_relay_state\":{\"state\":0}}}}";
public static final String COMMAND_INFO = "{\"system\":{\"get_sysinfo\":null}}";
/**
* Status an
*/
public static final int STATE_ON = 1;
/**
* Status aus
*/
public static final int STATE_OFF = 2;
/**
* IP Adresse der Steckdose
*/
private String ip;
/**
* Port
*/
private int port = 9999;
/**
* @param ip IP Adresse
*/
public HS100(String ip) {
this.ip = ip;
}
/**
* @param ip IP Adresse
* @param port TCP Port Nummer
*/
public HS100(String ip, int port) {
this.ip = ip;
this.port = port;
}
/**
* gibt die IP Adresse der Steckdose zurück
*
* @return IP Adresse
*/
public String getIp() {
return ip;
}
/**
* setzt die IP Adresse der Steckdose
*
* @param ip IP Adresse
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* gibt den Port der Steckdose zurück
*
* @return Port
*/
public int getPort() {
return port;
}
/**
* setzt den Port der Steckdose
*
* @param port Port
*/
public void setPort(int port) {
this.port = port;
}
/**
* prüft ob die Steckdose im Netzwerk erreichbar ist
*
* @return Erreichbarkeit
*/
public boolean isPresent() {
try {
InetAddress ip = InetAddress.getByName(getIp());
return ip.isReachable(500);
} catch (IOException ex) {}
return false;
}
/**
* prüft ob die Steckdose im Netzwerk erreichbar ist
*
* @param timeout Timeout (Wartezeit in ms)
* @return Erreichbarkeit
*/
public boolean isPresent(int timeout) {
try {
InetAddress ip = InetAddress.getByName(getIp());
return ip.isReachable(timeout);
} catch (IOException ex) {}
return false;
}
/**
* sendet einen Einschaltbefehl
*
* @return true bei Erfolg
*/
public boolean switchOn() throws IOException {
String jsonData = sendCommand(COMMAND_SWITCH_ON);
if(jsonData.length() > 0) {
JsonObject jo = new JsonParser().parse(jsonData).getAsJsonObject();
int errorCode = jo.get("system").getAsJsonObject().get("set_relay_state").getAsJsonObject().get("err_code").getAsInt();
return errorCode == 0;
}
return false;
}
/**
* sendet einen Ausschaltbefehl
*
* @return true bei Erfolg
*/
public boolean switchOff() throws IOException {
String jsonData = sendCommand(COMMAND_SWITCH_OFF);
if(jsonData.length() > 0) {
JsonObject jo = new JsonParser().parse(jsonData).getAsJsonObject();
int errorCode = jo.get("system").getAsJsonObject().get("set_relay_state").getAsJsonObject().get("err_code").getAsInt();
return errorCode == 0;
}
return false;
}
/**
* fragt den aktuellen Status der Steckodse ab
*
* @return STATE_ON oder STATE_OFF
*/
public int readState() throws IOException {
String jsonData = sendCommand(COMMAND_INFO);
if(jsonData.length() > 0) {
JsonObject jo = new JsonParser().parse(jsonData).getAsJsonObject();
int state = jo.get("system").getAsJsonObject().get("get_sysinfo").getAsJsonObject().get("relay_state").getAsInt();
return state == 1 ? STATE_ON : STATE_OFF;
}
return STATE_OFF;
}
/**
* gibt eine Map mit den Systeminformationen der Steckdose zurück
*
* @return Map mit Systeminformationen
*/
public Map<String, String> getInfo() throws IOException {
Map<String, String> result = new HashMap<>();
String jsonData = sendCommand(COMMAND_INFO);
if(jsonData.length() > 0) {
JsonObject jo = new JsonParser().parse(jsonData).getAsJsonObject();
JsonObject systemInfo = jo.get("system").getAsJsonObject().get("get_sysinfo").getAsJsonObject();
for(Map.Entry<String, JsonElement> entry : systemInfo.entrySet()) {
result.put(entry.getKey(), entry.getValue().getAsString());
}
}
return result;
}
/**
* sendet eine Befel an die Steckdose
*
* @param command Befehl
* @return Json STring mit dem Antwortobjekt
* @throws IOException
*/
protected String sendCommand(String command) throws IOException {
Socket socket = new Socket(ip, port);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(encryptWithHeader(command));
InputStream inputStream = socket.getInputStream();
String data = decrypt(inputStream);
outputStream.close();
inputStream.close();
socket.close();
return data;
}
private String decrypt(InputStream inputStream) throws IOException {
int in;
int key = 0x2B;
int nextKey;
StringBuilder sb = new StringBuilder();
while((in = inputStream.read()) != -1) {
nextKey = in;
in = in ^ key;
key = nextKey;
sb.append((char) in);
}
return "{" + sb.toString().substring(5);
}
private int[] encrypt(String command) {
int[] buffer = new int[command.length()];
int key = 0xAB;
for(int i = 0; i < command.length(); i++) {
buffer[i] = command.charAt(i) ^ key;
key = buffer[i];
}
return buffer;
}
private byte[] encryptWithHeader(String command) {
int[] data = encrypt(command);
byte[] bufferHeader = ByteBuffer.allocate(4).putInt(command.length()).array();
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferHeader.length + data.length).put(bufferHeader);
for(int in : data) {
byteBuffer.put((byte) in);
}
return byteBuffer.array();
}
}
package de.test;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
/**
* TP-Link HS100
*
* @author Oliver Kleditzsch
* @copyright Copyright (c) 2016, Oliver Kleditzsch
*/
public class HS110 extends HS100 {
private static final String COMMAND_ENERGY = "{\"emeter\":{\"get_realtime\":null}}";
/**
* Energiedaten
*/
public class EnergyData {
/**
* aktuelle Spannung in Volt
*/
private double nowVoltage;
/**
* aktueller Strom in Ampere
*/
private double nowCurrent;
/**
* aktuelle Leistung in Watt
*/
private double nowPower;
/**
* Energieverbrauch in Kilo Watt Stunden
*/
private double energy;
public EnergyData() {
}
/**
* @param nowVoltage aktuelle Spannung in Volt
* @param nowCurrent aktueller Strom in Ampere
* @param nowPower aktuelle Leistung in Watt
* @param energy Energieverbrauch in Kilo Watt Stunden
*/
public EnergyData(double nowVoltage, double nowCurrent, double nowPower, double energy) {
this.nowVoltage = nowVoltage;
this.nowCurrent = nowCurrent;
this.nowPower = nowPower;
this.energy = energy;
}
/**
* gibt die aktuelle Spannung zurück
*
* @return aktuelle Spannung in Volt
*/
public double getNowVoltage() {
return nowVoltage;
}
/**
* setzt die aktuelle Spannung
*
* @param nowVoltage aktuelle Spannung in Volt
*/
public void setNowVoltage(double nowVoltage) {
this.nowVoltage = nowVoltage;
}
/**
* gibt den aktuellen Strom zurück
*
* @return aktueller Strom in Ampere
*/
public double getNowCurrent() {
return nowCurrent;
}
/**
* setzt den aktuellen Strom
*
* @param nowCurrent aktueller Strom in Ampere
*/
public void setNowCurrent(double nowCurrent) {
this.nowCurrent = nowCurrent;
}
/**
* gibt die aktuelle Leistung zurück
*
* @return aktuelle Leistung in Watt
*/
public double getNowPower() {
return nowPower;
}
/**
* setzt die aktuelle Leistung
*
* @param nowPower aktuelle Leistung in Watt
*/
public void setNowPower(double nowPower) {
this.nowPower = nowPower;
}
/**
* gibt den Energieverbrauch zurück
*
* @return Energieverbrauch in Kilo Watt Stunden
*/
public double getEnergy() {
return energy;
}
/**
* setzt den Energieverbrauch
*
* @param energy Energieverbrauch in Kilo Watt Stunden
*/
public void setEnergy(double energy) {
this.energy = energy;
}
}
/**
* @param ip IP Adresse
*/
public HS110(String ip) {
super(ip);
}
/**
* @param ip IP Adresse
* @param port TCP Port Nummer
*/
public HS110(String ip, int port) {
super(ip, port);
}
/**
* gint die Energiedaten zurück
*
* @return Energiedaten
* @throws IOException
*/
public EnergyData getEnergyData() throws IOException {
EnergyData energyData = new EnergyData();
String jsonData = sendCommand(COMMAND_ENERGY);
if(jsonData.length() > 0) {
JsonObject jo = new JsonParser().parse(jsonData).getAsJsonObject();
JsonObject energyDataJson = jo.get("emeter").getAsJsonObject().get("get_realtime").getAsJsonObject();
energyData.setNowCurrent(energyDataJson.get("current").getAsDouble());
energyData.setNowVoltage(energyDataJson.get("voltage").getAsDouble());
energyData.setNowPower(energyDataJson.get("power").getAsDouble());
energyData.setEnergy(energyDataJson.get("total").getAsDouble());
}
return energyData;
}
}
package de.test;
/**
* TP-Link HS100/HS110 Beispielprogramm
*
* @author Oliver Kleditzsch
* @copyright Copyright (c) 2016, Oliver Kleditzsch
*/
import java.io.*;
import java.util.Map;
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
HS110 hs100 = new HS110("192.168.10.20");
System.out.println("Switch On");
hs100.switchOn();
System.out.println("new state: " + (hs100.readState() == HS100.STATE_ON ? "on" : "off"));
System.out.println();
Thread.sleep(2000);
System.out.println("Switch Off");
hs100.switchOff();
System.out.println("new state: " + (hs100.readState() == HS100.STATE_ON ? "on" : "off"));
System.out.println();
Thread.sleep(2000);
System.out.println("Switch On");
hs100.switchOn();
System.out.println("new state: " + (hs100.readState() == HS100.STATE_ON ? "on" : "off"));
System.out.println();
System.out.println("Energy Data:");
HS110.EnergyData energyData = hs100.getEnergyData();
System.out.println("Voltage: " + energyData.getNowVoltage() + "V");
System.out.println("Current: " + energyData.getNowCurrent() + "A");
System.out.println("Power: " + energyData.getNowPower() + "W");
System.out.println("Energy: " + energyData.getEnergy() + "kWh");
System.out.println();
System.out.println("System Data:");
Map<String, String> sysinfo = hs100.getInfo();
for(Map.Entry<String, String> entry : sysinfo.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}
@jens-234
Copy link

Basically this worked for me. I had just one weird problem: After a command or query was sent and the response had entirely come in the channel wasn't shutdown. So the while((in = inputStream.read()) != -1) fired only after about a minute in my case. To change that I adapted the function so it returns once as many closing brackets have come in as have opening (-1 the one that is only added at the end).

private String decrypt(InputStream inputStream) throws IOException
{
int in;
int key = 0x2B;
int nextKey;
StringBuilder sb = new StringBuilder();
try
{
boolean allBracketsClosed = false;
while(!allBracketsClosed && (in = inputStream.read()) != -1)
{
nextKey = in;
in = in ^ key;
key = nextKey;
sb.append((char) in);
// System.out.println((char) in + " " + sb.chars().filter(num -> num == '{').count() + " " + sb.chars().filter(num -> num == '}').count());
if(sb.length() > 0)
{
allBracketsClosed = sb.chars().filter(num -> num == '{').count() > 0
&&
(
sb.chars().filter(num -> num == '{').count()
==
sb.chars().filter(num -> num == '}').count() -1
);
}
}
}
catch(SocketTimeoutException e)
{}
// System.out.println("done");
return "{" + sb.toString().substring(5);
}

@4thFourthHorseman
Copy link

Hallo,
funktioniert der Code mit der aktuellen Firmware der Hs100 noch?

@agent4788
Copy link
Author

kann ich nicht genau sagen, ich habe schon eweig kein FW Update mehr gemacht. Bei den letzten Updates hat es aber immer funktioniert.

@timkn
Copy link

timkn commented Mar 13, 2022

Zumindest switch on/off funktioniert noch mit der aktuellsten Version (bei mir 1.5.6).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment