Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tplinksmarthome] Added support for power outlets HS107, HS300, KP200, KP400 #5716

Merged
merged 2 commits into from
Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A second question: Why don't you use UoM here. Are there plans to introduce it for this binding.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about that too recently 😄 The original implementation (for HS110) is from before UoM and the others just reuse the code. I was thinking about making them UoM, although it's also a breaking change for the HS110. But I was planning to change it after this pr was being merged.

| 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());
Hilbrand marked this conversation as resolved.
Show resolved Hide resolved

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