Skip to content

Commit

Permalink
[tplinksmarthome] Added support for power outlets HS107, HS300, KP200…
Browse files Browse the repository at this point in the history
…, KP400 (openhab#5716)

Closes openhab#5051

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
Signed-off-by: Maximilian Hess <mail@ne0h.de>
  • Loading branch information
Hilbrand authored and ne0h committed Sep 15, 2019
1 parent ce94752 commit ad45eee
Show file tree
Hide file tree
Showing 46 changed files with 965 additions and 233 deletions.
3 changes: 2 additions & 1 deletion bundles/org.openhab.binding.tplinksmarthome/.classpath
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
Expand Down
64 changes: 44 additions & 20 deletions bundles/org.openhab.binding.tplinksmarthome/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ The following TP-Link Smart Devices are supported:

### HS107 Smart Wi-Fi Plug, 2-Outlets

Not supported yet.
* Switch On/Off Group
* Switch On/Off Outlets
* Led On/Off
* Wi-Fi signal strength (rssi)

### HS110 Smart Wi-Fi Plug

Expand Down Expand Up @@ -64,7 +67,11 @@ Switching via openHAB activates the switch directly.

### HS300 Smart Wi-Fi Power Strip

Not supported yet.
* Switch On/Off Group
* Switch On/Off Outlets
* Energy readings Outlets
* Led On/Off
* Wi-Fi signal strength (rssi)

### KB100 Kasa Smart Light Bulb

Expand Down Expand Up @@ -94,11 +101,17 @@ Switching, Brightness and Color is done using the `color` channel.

### KP200 Smart Wi-Fi Power Outlet, 2-Sockets

Not supported yet.
* Switch On/Off Group
* Switch On/Off Outlets
* Led On/Off
* Wi-Fi signal strength (rssi)

### KP400 Smart Outdoor Plug

Not supported yet.
* Switch On/Off Group
* Switch On/Off Outlets
* Led On/Off
* Wi-Fi signal strength (rssi)

### LB100 Smart Wi-Fi LED Bulb with Dimmable Light

Expand Down Expand Up @@ -241,33 +254,44 @@ Either `deviceId` or `ipAddress` must be set.

All devices support some of the following channels:

| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|------------------|-----------|----------------------------------------------------|-----------------------------------------------------------------|
| switch | Switch | Switch the Smart Home device on or off. | HS100, HS103, HS105, HS110, HS200, HS210, KP100, RE270K, RE370K |
| brightness | Dimmer | Set the brightness of Smart Home device or dimmer. | HS220, KB100, KL110, KL120, LB100, LB110, LB120, LB200 |
| colorTemperature | Dimmer | Set the color temperature of Smart Home light. | KB130, KL120, KL130, LB120, LB130, LB230 |
| color | Color | Set the color of the Smart Home light. | KB130, KL130, LB130, LB230 |
| power | Number | Actual energy usage in Watt. | HS110, KLxxx, LBxxx |
| eneryUsage | Number | Energy Usage in kWh. | HS110 |
| current | Number | Actual current usage in Ampere. | HS110 |
| voltage | Number | Actual voltage usage in Volt. | HS110 |
| led | Switch | Switch the status led on the device on or off. | HS100, HS103, HS105, HS110, HS200, HS210, HS220, KP100 |
| rssi | Number | Wi-Fi signal strength indicator in dBm. | All |
| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|------------------|-----------|----------------------------------------------------|---------------------------------------------------------------------------------------------|
| switch | Switch | Switch the Smart Home device on or off. | HS100, HS103, HS105, HS107, HS110, HS200, HS210, HS300, KP100, KP200, KP400, RE270K, RE370K |
| brightness | Dimmer | Set the brightness of Smart Home device or dimmer. | HS220, KB100, KL110, KL120, LB100, LB110, LB120, LB200 |
| colorTemperature | Dimmer | Set the color temperature of Smart Home light. | KB130, KL120, KL130, LB120, LB130, LB230 |
| color | Color | Set the color of the Smart Home light. | KB130, KL130, LB130, LB230 |
| power | Number | Actual energy usage in Watt. | HS110, HS300, KLxxx, LBxxx |
| eneryUsage | Number | Energy Usage in kWh. | HS110, HS300 |
| current | Number | Actual current usage in Ampere. | HS110, HS300 |
| voltage | Number | Actual voltage usage in Volt. | HS110, HS300 |
| led | Switch | Switch the status led on the device on or off. | HS100, HS103, HS105, HS107, HS110, HS200, HS210, HS220, HS300, KP100, KP200, KP400 |
| rssi | Number | Wi-Fi signal strength indicator in dBm. | All |

The outlet devices (HS107, HS300, KP200, KP400) have group channels.
This means the channel is prefixed with the group id.
The following group ids are available:

| Group ID | Description |
|-------------------|-------------------------------------------------------------------------------------------------------|
| groupSwitch | General channels. e.g. `groupSwitch#switch` |
| outlet&lt;number> | The outlet to control. &lt;number> is the number of the outlet (starts with 1). e.g. `outlet1#switch` |

## Full Example

### tplinksmarthome.things:

```
tplinksmarthome:hs100:tv "Living Room" [ deviceId="00000000000000000000000000000001", refresh=60 ]
tplinksmarthome:lb110:bulb1 "Living Room Bulb 1" [ deviceId="00000000000000000000000000000002", refresh=60, transitionPeriod=2500 ]
tplinksmarthome:lb130:bulb2 "Living Room Bulb 2" [ deviceId="00000000000000000000000000000003", refresh=60, transitionPeriod=2500 ]
tplinksmarthome:hs100:tv "TV" [ deviceId="00000000000000000000000000000001", refresh=60 ]
tplinksmarthome:hs300:laptop "Laptop" [ deviceId="00000000000000000000000000000004", refresh=60 ]
tplinksmarthome:lb110:bulb1 "Living Room Bulb 1" [ deviceId="00000000000000000000000000000002", refresh=60, transitionPeriod=2500 ]
tplinksmarthome:lb130:bulb2 "Living Room Bulb 2" [ deviceId="00000000000000000000000000000003", refresh=60, transitionPeriod=2500 ]
```

### tplinksmarthome.items:

```
Switch TP_L_Switch "Switch" { channel="tplinksmarthome:hs100:tv:switch" }
Switch TP_L_TV "TV" { channel="tplinksmarthome:hs100:tv:switch" }
Switch TP_L_Laptop "Laptop" { channel="tplinksmarthome:hs300:laptop:outlet1#switch" }
Number TP_L_RSSI "Signal [%d] dB" <signal> { channel="tplinksmarthome:hs100:tv:rssi" }
Dimmer TP_LB_Bulb "Dimmer [%d %%]" <slider> { channel="tplinksmarthome:lb110:bulb1:brightness" }
Dimmer TP_LB_ColorT "Color Temperature [%d] %%" <slider> { channel="tplinksmarthome:lb130:bulb2:colorTemperature" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.binding.tplinksmarthome.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.HSBType;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.openhab.binding.tplinksmarthome.internal.model.GetRealtime;
Expand Down Expand Up @@ -43,10 +44,11 @@
@NonNullByDefault
public class Commands {

private static final String CONTEXT = "{\"context\":{\"child_ids\":[\"%s\"]},";
private static final String SYSTEM_GET_SYSINFO = "\"system\":{\"get_sysinfo\":{}}";
private static final String GET_SYSINFO = "{" + SYSTEM_GET_SYSINFO + "}";
private static final String GET_REALTIME_AND_SYSINFO = "{" + SYSTEM_GET_SYSINFO
+ ", \"emeter\":{\"get_realtime\":{}}}";
private static final String REALTIME = "\"emeter\":{\"get_realtime\":{}}";
private static final String GET_REALTIME_AND_SYSINFO = "{" + SYSTEM_GET_SYSINFO + ", " + REALTIME + "}";
private static final String GET_REALTIME_BULB_AND_SYSINFO = "{" + SYSTEM_GET_SYSINFO
+ ", \"smartlife.iot.common.emeter\":{\"get_realtime\":{}}}";

Expand All @@ -71,6 +73,16 @@ public static String getRealtimeBulbAndSysinfo() {
return GET_REALTIME_BULB_AND_SYSINFO;
}

/**
* Returns the json to get the energy and sys info data from an outlet device.
*
* @param id optional id of the device
* @return The json string of the command to send to the device
*/
public static String getRealtimeWithContext(String id) {
return String.format(CONTEXT, id) + REALTIME + "}";
}

/**
* Returns the json response of the get_realtime command to the data object.
*
Expand Down Expand Up @@ -108,11 +120,15 @@ public Sysinfo getSysinfoReponse(String getSysinfoReponse) {
* Returns the json for the set_relay_state command to switch on or off.
*
* @param onOff the switch state to set
* @param childId optional child id if multiple children are supported by a single device
* @return The json string of the command to send to the device
*/
public String setRelayState(OnOffType onOff) {
public String setRelayState(OnOffType onOff, @Nullable String childId) {
SetRelayState relayState = new SetRelayState();
relayState.setRelayState(onOff);
if (childId != null) {
relayState.setChildId(childId);
}
return gsonWithExpose.toJson(relayState);
}

Expand All @@ -122,7 +138,7 @@ public String setRelayState(OnOffType onOff) {
* @param relayStateResponse the json string
* @return The data object containing the state data from the json string
*/
public SetRelayState setRelayStateResponse(String relayStateResponse) {
public @Nullable SetRelayState setRelayStateResponse(String relayStateResponse) {
return gsonWithExpose.fromJson(relayStateResponse, SetRelayState.class);
}

Expand All @@ -144,7 +160,7 @@ public String setSwitchState(OnOffType onOff) {
* @param switchStateResponse the json string
* @return The data object containing the state data from the json string
*/
public SetSwitchState setSwitchStateResponse(String switchStateResponse) {
public @Nullable SetSwitchState setSwitchStateResponse(String switchStateResponse) {
return gsonWithExpose.fromJson(switchStateResponse, SetSwitchState.class);
}

Expand All @@ -166,7 +182,7 @@ public String setDimmerBrightness(int brightness) {
* @param dimmerBrightnessResponse the json string
* @return The data object containing the state data from the json string
*/
public HasErrorResponse setDimmerBrightnessResponse(String dimmerBrightnessResponse) {
public @Nullable HasErrorResponse setDimmerBrightnessResponse(String dimmerBrightnessResponse) {
return gsonWithExpose.fromJson(dimmerBrightnessResponse, SetBrightness.class);
}

Expand All @@ -190,11 +206,15 @@ public String setLightState(OnOffType onOff, int transitionPeriod) {
* Returns the json for the set_led_off command to switch the led of the device on or off.
*
* @param onOff the led state to set
* @param childId optional child id if multiple children are supported by a single device
* @return The json string of the command to send to the device
*/
public String setLedOn(OnOffType onOff) {
public String setLedOn(OnOffType onOff, @Nullable String childId) {
SetLedOff sLOff = new SetLedOff();
sLOff.setLed(onOff);
if (childId != null) {
sLOff.setChildId(childId);
}
return gsonWithExpose.toJson(sLOff);
}

Expand All @@ -204,7 +224,7 @@ public String setLedOn(OnOffType onOff) {
* @param setLedOnResponse the json string
* @return The data object containing the data from the json string
*/
public SetLedOff setLedOnResponse(String setLedOnResponse) {
public @Nullable SetLedOff setLedOnResponse(String setLedOnResponse) {
return gsonWithExpose.fromJson(setLedOnResponse, SetLedOff.class);
}

Expand Down Expand Up @@ -268,7 +288,7 @@ public String setColorTemperature(int colorTemperature, int transitionPeriod) {
* @param response the json string
* @return The data object containing the state data from the json string
*/
public TransitionLightStateResponse setTransitionLightStateResponse(String response) {
public @Nullable TransitionLightStateResponse setTransitionLightStateResponse(String response) {
return gson.fromJson(response, TransitionLightStateResponse.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public String sendCommand(String command) throws IOException {
logger.trace("Executing command: {}", command);
try (Socket socket = createSocket(); final OutputStream outputStream = socket.getOutputStream()) {
outputStream.write(CryptUtil.encryptWithLength(command));
String response = readReturnValue(socket);
final String response = readReturnValue(socket);

logger.trace("Command response: {}", response);
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class TPLinkSmartHomeBindingConstants {

public static final String BINDING_ID = "tplinksmarthome";

// List of all channel ids
// List of all switch channel ids
public static final String CHANNEL_SWITCH = "switch";

// List of all plug channel ids
Expand Down Expand Up @@ -60,6 +60,10 @@ public final class TPLinkSmartHomeBindingConstants {
// List of all misc channel ids
public static final String CHANNEL_RSSI = "rssi";

// List of all group channel ids
public static final String CHANNEL_SWITCH_GROUP = "group";
public static final String CHANNEL_OUTLET_GROUP_PREFIX = "outlet";

// List of configuration keys
public static final String CONFIG_IP = "ipAddress";
public static final String CONFIG_DEVICE_ID = "deviceId";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
* @author Hilbrand Bouwkamp - Complete make-over, reorganized code and code cleanup.
*/
@Component(service = { DiscoveryService.class,
TPLinkIpAddressService.class }, immediate = true, configurationPid = "discovery.tplinksmarthome")
TPLinkIpAddressService.class }, immediate = true, configurationPid = "discovery.tplinksmarthome")
@NonNullByDefault
public class TPLinkSmartHomeDiscoveryService extends AbstractDiscoveryService implements TPLinkIpAddressService {

Expand All @@ -69,7 +69,7 @@ public class TPLinkSmartHomeDiscoveryService extends AbstractDiscoveryService im
public TPLinkSmartHomeDiscoveryService() throws UnknownHostException {
super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SECONDS);
InetAddress broadcast = InetAddress.getByName(BROADCAST_IP);
byte[] discoverbuffer = CryptUtil.encrypt(Commands.getSysinfo());
final byte[] discoverbuffer = CryptUtil.encrypt(Commands.getSysinfo());
discoverPacket = new DatagramPacket(discoverbuffer, discoverbuffer.length, broadcast,
Connection.TP_LINK_SMART_HOME_PORT);
}
Expand Down Expand Up @@ -106,7 +106,7 @@ protected void startScan() {
if (discoverSocket == null) {
break;
}
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

discoverSocket.receive(packet);
logger.debug("TP-Link Smart device discovery returned package with length {}", packet.getLength());
Expand Down Expand Up @@ -139,7 +139,8 @@ protected void stopScan() {
* @throws IOException exception in case sending the packet failed
*/
protected DatagramSocket sendDiscoveryPacket() throws IOException {
DatagramSocket ds = new DatagramSocket(null);
final DatagramSocket ds = new DatagramSocket(null);

ds.setBroadcast(true);
ds.setSoTimeout(UDP_PACKET_TIMEOUT_MS);
ds.send(discoverPacket);
Expand All @@ -165,24 +166,25 @@ private void closeDiscoverSocket() {
* @throws IOException in case decrypting of the data failed
*/
private void detectThing(DatagramPacket packet) throws IOException {
String ipAddress = packet.getAddress().getHostAddress();
String rawData = CryptUtil.decrypt(packet.getData(), packet.getLength());
Sysinfo sysinfoRaw = commands.getSysinfoReponse(rawData);
Sysinfo sysinfo = sysinfoRaw.getActualSysinfo();
final String ipAddress = packet.getAddress().getHostAddress();
final String rawData = CryptUtil.decrypt(packet.getData(), packet.getLength());
final Sysinfo sysinfoRaw = commands.getSysinfoReponse(rawData);
final Sysinfo sysinfo = sysinfoRaw.getActualSysinfo();

logger.trace("Detected TP-Link Smart Home device: {}", rawData);
String deviceId = sysinfo.getDeviceId();
final String deviceId = sysinfo.getDeviceId();
logger.debug("TP-Link Smart Home device '{}' with id {} found on {} ", sysinfo.getAlias(), deviceId, ipAddress);
idInetAddressCache.put(deviceId, ipAddress);
Optional<ThingTypeUID> thingTypeUID = getThingTypeUID(sysinfo.getModel());
final Optional<ThingTypeUID> thingTypeUID = getThingTypeUID(sysinfo.getModel());

if (thingTypeUID.isPresent()) {
ThingUID thingUID = new ThingUID(thingTypeUID.get(),
final ThingUID thingUID = new ThingUID(thingTypeUID.get(),
deviceId.substring(deviceId.length() - 6, deviceId.length()));
Map<String, Object> properties = PropertiesCollector.collectProperties(thingTypeUID.get(), ipAddress,
final Map<String, Object> properties = PropertiesCollector.collectProperties(thingTypeUID.get(), ipAddress,
sysinfoRaw);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withLabel(sysinfo.getAlias())
.withRepresentationProperty(deviceId).withProperties(properties).build();
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withLabel(sysinfo.getAlias()).withRepresentationProperty(deviceId).withProperties(properties)
.build();
thingDiscovered(discoveryResult);
} else {
logger.debug("Detected, but ignoring unsupported TP-Link Smart Home device model '{}'", sysinfo.getModel());
Expand All @@ -196,7 +198,7 @@ private void detectThing(DatagramPacket packet) throws IOException {
* @return {@link ThingTypeUID} or null if device not recognized
*/
private Optional<ThingTypeUID> getThingTypeUID(String model) {
String modelLC = model.toLowerCase(Locale.ENGLISH);
final String modelLC = model.toLowerCase(Locale.ENGLISH);
return SUPPORTED_THING_TYPES.stream().filter(suid -> modelLC.startsWith(suid.getId())).findFirst();
}
}
Loading

0 comments on commit ad45eee

Please sign in to comment.