added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</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"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.tplinksmarthome</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,345 @@
# TP-Link Smart Home Binding
This binding adds support to control TP-Link Smart Home Devices from your local openHAB system.
## Supported Things
The following TP-Link Smart Devices are supported:
### HS100 Smart Wi-Fi Plug
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS103 Smart Wi-Fi Plug Lite
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS105 Smart Wi-Fi Plug
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS107 Smart Wi-Fi Plug, 2-Outlets
* Power On/Off Group
* Power On/Off Outlets
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS110 Smart Wi-Fi Plug
* Power On/Off
* Energy readings
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS200 Smart Wi-Fi Switch
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS210 Smart Wi-Fi Light Switch 3-Way Kit
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### HS220 Smart Wi-Fi Light Switch, Dimmer
* Power On/Off
* Adjust the brightness
* LED On/Off
* Wi-Fi signal strength (RSSI)
Use the brightness channel on the HS220 with a Switch item can be used to switch the device on and off.
It will not change the brightness value.
The default refresh for switch devices is set to 1 second. So it polls the switch for status changes.
If you don't use the switch manually often, you can set it to a higher refresh.
The refresh is only relevant to detect manual using the switch.
Switching via openHAB activates the switch directly.
### HS300 Smart Wi-Fi Power Strip
* Power On/Off Group
* Power On/Off Outlets
* Energy readings Outlets
* LED On/Off
* Wi-Fi signal strength (RSSI)
### KB100 Kasa Smart Light Bulb
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### KB130 Kasa Multi-color Smart Light Bulb
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching, Brightness and Color is done using the `color` channel.
### KP100 Kasa Wi-Fi Smart Plug - Slim Edition
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### KP105 Kasa Wi-Fi Smart Plug - Slim Edition
* Power On/Off
* LED On/Off
* Wi-Fi signal strength (RSSI)
### KP200 Smart Wi-Fi Power Outlet, 2-Sockets
* Power On/Off Group
* Power On/Off Outlets
* LED On/Off
* Wi-Fi signal strength (RSSI)
### KP303 Smart Wi-Fi Power Outlet, 3-Sockets
* Power On/Off Group
* Power On/Off Outlets
* LED On/Off
* Wi-Fi signal strength (RSSI)
### KP400 Smart Outdoor Plug
* Power On/Off Group
* Power On/Off Outlets
* LED On/Off
* Wi-Fi signal strength (RSSI)
### LB100 Smart Wi-Fi LED Bulb with Dimmable Light
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### LB110 Smart Wi-Fi LED Bulb with Dimmable Light
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### LB120 Smart Wi-Fi LED Bulb with Tunable White Light
* Power On/Off
* Adjust light appearance from soft white (2700k) to daylight (6500k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### LB130 Smart Wi-Fi LED Bulb with Color Changing Hue
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching, Brightness and Color is done using the `color` channel.
### LB200 Smart Wi-Fi LED Bulb with Dimmable Light
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### LB230 Smart Wi-Fi LED Bulb with Color Changing Hue
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching, Brightness and Color is done using the `color` channel.
### KL50 Kasa Filament Smart Bulb, Soft White
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### KL60 Kasa Filament Smart Bulb, Warm Amber
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### KL110 Smart Wi-Fi LED Bulb with Dimmable Light
* Power On/Off
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### KL120 Smart Wi-Fi LED Bulb with Tunable White Light
* Power On/Off
* Adjust light appearance from soft white (2700k) to daylight (6500k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching and Brightness is done using the `brightness` channel.
### KL130 Smart Wi-Fi LED Bulb with Color Changing Hue
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* Actual power usage
* Wi-Fi signal strength (RSSI)
Switching, Brightness and Color is done using the `color` channel.
### RE270K AC750 Wi-Fi Range Extender with Smart Plug
* Power On/Off (readonly)
* Wi-Fi signal strength (RSSI)
### RE370K AC1200 Wi-Fi Range Extender with Smart Plug
* Power On/Off (readonly)
* Wi-Fi signal strength (RSSI)
It is not possible to set the switch state on the Range Extender.
This is because it is not known what command to send to the device to make this possible.
## Prerequisites
Before using Smart Plugs with openHAB the devices must be connected to the Wi-Fi network.
This can be done using the TP-Link provided mobile app Kasa.
## Discovery
Devices can be auto discovered in the same local network as the openHAB application.
It is possible to connect to devices on a different network, but these must be added manually by `ipAddress`.
It is not possible to connect to devices on a different network using `deviceId` as configuration.
## Thing Configuration
The thingId is the product type in lower case. For example `HS100` has thingId `hs100`.
The thing can be configured by `ipAddress` or by `deviceId`.
If the one of them is used the other is automatically set by the binding.
When manually configured it is preferred to set the `deviceId` because if the ip address of the device would change this will be automatically updated.
The `deviceId` is the unique identifier each TP-Link device has.
The `deviceId` can be seen when using discovery in openHAB.
Discovery will set the `deviceId` automatically.
Using a configuration with `deviceId` depends on the discovery service of the binding.
The binding supports background discovery and this will update the ip address in case it changes within a minute.
With background discovery disabled the ip address, which is needed to communicate with the device, needs to be set by starting a manual discovery.
It will not update the ip address if background discovery is disabled and the ip address of the device changes.
Manually starting a discovery can also be used to set the ip address directly instead of waiting for the 1 minute background discovery refresh period.
The thing has the following configuration parameters:
| Parameter | Description |
|--------------------|----------------------------------------------------------------------------------------------|
| deviceId | The TP-Link id of the device. |
| ipAddress | IP Address of the device. |
| refresh | Refresh interval in seconds. Optional. The default is 30 seconds, and 1 second for switches. |
| transitionPeriod | Duration of state changes in milliseconds, only for light bulbs, default 0. |
Either `deviceId` or `ipAddress` must be set.
## Channels
All devices support some of the following channels:
| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|---------------------|--------------------------|------------------------------------------------|----------------------------------------------------------------------------------------------------|
| switch | Switch | Power the device on or off. | HS100, HS103, HS105, HS107, HS110, HS200, HS210, HS300, KP100, KP105, KP200, KP303, KP400, RE270K, RE370K |
| brightness | Dimmer | Set the brightness of device or dimmer. | HS220, KB100, KL50, KL60, KL110, KL120, LB100, LB110, LB120, LB200 |
| colorTemperature | Dimmer | Set the color temperature in percentage. | KB130, KL120, KL130, LB120, LB130, LB230 |
| colorTemperatureAbs | Number | Set the color temperature in Kelvin. | KB130, KL120, KL130, LB120, LB130, LB230 |
| color | Color | Set the color of the light. | KB130, KL130, LB130, LB230 |
| power | Number:Power | Actual energy usage in Watt. | HS110, HS300, KLxxx, LBxxx |
| eneryUsage | Number:Energy | Energy Usage in kWh. | HS110, HS300 |
| current | Number:ElectricCurrent | Actual current usage in Ampere. | HS110, HS300 |
| voltage | Number:ElectricPotential | 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, KP105, KP303, KP200, KP400 |
| rssi | Number:Power | 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 | Group id for all 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` |
### Channel Refresh
When the thing receives a `RefreshType` command the channel state is updated from an internal cache.
This cache is updated per refresh interval as configured in the thing.
However for some use cases it is preferable to set the refresh interval higher than the default.
For example for switches the 1 second refresh interval may cause a burden to the network traffic.
Therefore if the refresh interval for switches is set to a value higher than 5 seconds, and for the other devices higher than 1 minute.
Than the a `RefreshType` command will fetch the device state and update the internal cache.
## Full Example
### tplinksmarthome.things:
```
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_TV "TV" { channel="tplinksmarthome:hs100:tv:switch" }
Switch TP_L_Laptop "Laptop" { channel="tplinksmarthome:hs300:laptop:outlet1#switch" }
Number:Power TP_L_RSSI "Signal [%d %unit%]" <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" }
Color TP_LB_Color "Color" <slider> { channel="tplinksmarthome:lb130:bulb2:color" }
Switch TP_LB_ColorS "Switch" { channel="tplinksmarthome:lb130:bulb2:color" }
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.tplinksmarthome</artifactId>
<name>openHAB Add-ons :: Bundles :: TP-Link Smart Home Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.tplinksmarthome-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-tplinksmarthome" description="TP-Link Smart Home Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tplinksmarthome/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,294 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.model.GetRealtime;
import org.openhab.binding.tplinksmarthome.internal.model.GetSysinfo;
import org.openhab.binding.tplinksmarthome.internal.model.GsonUtil;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.binding.tplinksmarthome.internal.model.Realtime;
import org.openhab.binding.tplinksmarthome.internal.model.SetBrightness;
import org.openhab.binding.tplinksmarthome.internal.model.SetLedOff;
import org.openhab.binding.tplinksmarthome.internal.model.SetRelayState;
import org.openhab.binding.tplinksmarthome.internal.model.SetSwitchState;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightState;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightState.LightOnOff;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightState.LightStateBrightness;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightState.LightStateColor;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightState.LightStateColorTemperature;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightStateResponse;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.Gson;
/**
* Class to construct the tp-link json commands and convert retrieved results into data objects.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Rewritten to use gson to parse json
*/
@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 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\":{}}}";
private final Gson gson = GsonUtil.createGson();
private final Gson gsonWithExpose = GsonUtil.createGsonWithExpose();
/**
* Returns the json to get the energy and sys info data from the device.
*
* @return The json string of the command to send to the device
*/
public static String getRealtimeAndSysinfo() {
return GET_REALTIME_AND_SYSINFO;
}
/**
* Returns the json to get the energy and sys info data from the bulb.
*
* @return The json string of the command to send to the bulb
*/
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.
*
* @param realtimeResponse the json string
* @return The data object containing the energy data from the json string
*/
@SuppressWarnings("null")
public Realtime getRealtimeResponse(String realtimeResponse) {
GetRealtime getRealtime = gson.fromJson(realtimeResponse, GetRealtime.class);
return getRealtime == null ? new Realtime() : getRealtime.getRealtime();
}
/**
* Returns the json to get the sys info data from the device.
*
* @return The json string of the command to send to the device
*/
public static String getSysinfo() {
return GET_SYSINFO;
}
/**
* Returns the json response of the get_sysinfo command to the data object.
*
* @param getSysinfoReponse the json string
* @return The data object containing the state data from the json string
*/
@SuppressWarnings("null")
public Sysinfo getSysinfoReponse(String getSysinfoReponse) {
GetSysinfo getSysinfo = gson.fromJson(getSysinfoReponse, GetSysinfo.class);
return getSysinfo == null ? new Sysinfo() : getSysinfo.getSysinfo();
}
/**
* 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, @Nullable String childId) {
SetRelayState relayState = new SetRelayState();
relayState.setRelayState(onOff);
if (childId != null) {
relayState.setChildId(childId);
}
return gsonWithExpose.toJson(relayState);
}
/**
* Returns the json response of the set_relay_state command to the data object.
*
* @param relayStateResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable SetRelayState setRelayStateResponse(String relayStateResponse) {
return gsonWithExpose.fromJson(relayStateResponse, SetRelayState.class);
}
/**
* Returns the json for the set_switch_state command to switch a dimmer on or off.
*
* @param onOff the switch state to set
* @return The json string of the command to send to the device
*/
public String setSwitchState(OnOffType onOff) {
SetSwitchState switchState = new SetSwitchState();
switchState.setSwitchState(onOff);
return gsonWithExpose.toJson(switchState);
}
/**
* Returns the json response of the set_switch_state command to the data object.
*
* @param switchStateResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable SetSwitchState setSwitchStateResponse(String switchStateResponse) {
return gsonWithExpose.fromJson(switchStateResponse, SetSwitchState.class);
}
/**
* Returns the json for the set_brightness command to set the brightness value.
*
* @param brightness the brightness value to set
* @return The json string of the command to send to the device
*/
public String setDimmerBrightness(int brightness) {
SetBrightness setBrightness = new SetBrightness();
setBrightness.setBrightness(brightness);
return gsonWithExpose.toJson(setBrightness);
}
/**
* Returns the json response of the set_brightness command to the data object.
*
* @param dimmerBrightnessResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable HasErrorResponse setDimmerBrightnessResponse(String dimmerBrightnessResponse) {
return gsonWithExpose.fromJson(dimmerBrightnessResponse, SetBrightness.class);
}
/**
* Returns the json for the set_light_state command to switch a bulb on or off.
*
* @param onOff the switch state to set
* @param transitionPeriod the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setLightState(OnOffType onOff, int transitionPeriod) {
TransitionLightState transitionLightState = new TransitionLightState();
LightOnOff lightState = new LightOnOff();
lightState.setOnOff(onOff);
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
}
/**
* 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, @Nullable String childId) {
SetLedOff sLOff = new SetLedOff();
sLOff.setLed(onOff);
if (childId != null) {
sLOff.setChildId(childId);
}
return gsonWithExpose.toJson(sLOff);
}
/**
* Returns the json response for the set_led_off command to the data object.
*
* @param setLedOnResponse the json string
* @return The data object containing the data from the json string
*/
public @Nullable SetLedOff setLedOnResponse(String setLedOnResponse) {
return gsonWithExpose.fromJson(setLedOnResponse, SetLedOff.class);
}
/**
* Returns the json for the set_light_State command to set the brightness.
*
* @param brightness the brightness value
* @param transitionPeriod the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setBrightness(int brightness, int transitionPeriod) {
TransitionLightState transitionLightState = new TransitionLightState();
LightStateBrightness lightState = new LightStateBrightness();
lightState.setOnOff(brightness == 0 ? OnOffType.OFF : OnOffType.ON);
lightState.setBrightness(brightness);
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
}
/**
* Returns the json for the set_light_State command to set the color.
*
* @param hsb the color to set
* @param transitionPeriod the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setColor(HSBType hsb, int transitionPeriod) {
TransitionLightState transitionLightState = new TransitionLightState();
LightStateColor lightState = new LightStateColor();
int brightness = hsb.getBrightness().intValue();
lightState.setOnOff(brightness == 0 ? OnOffType.OFF : OnOffType.ON);
lightState.setBrightness(brightness);
lightState.setHue(hsb.getHue().intValue());
lightState.setSaturation(hsb.getSaturation().intValue());
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
}
/**
* Returns the json for the set_light_State command to set the color temperature.
*
* @param colorTemperature the color temperature to set
* @param transitionPeriod the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setColorTemperature(int colorTemperature, int transitionPeriod) {
TransitionLightState transitionLightState = new TransitionLightState();
LightStateColorTemperature lightState = new LightStateColorTemperature();
lightState.setOnOff(OnOffType.ON);
lightState.setColorTemperature(colorTemperature);
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
}
/**
* Returns the json response for the set_light_state command.
*
* @param response the json string
* @return The data object containing the state data from the json string
*/
public @Nullable TransitionLightStateResponse setTransitionLightStateResponse(String response) {
return gson.fromJson(response, TransitionLightStateResponse.class);
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class acts as and interface to the physical device.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Reorganized code an put connection in single class
*/
@NonNullByDefault
public class Connection {
public static final int TP_LINK_SMART_HOME_PORT = 9999;
private static final int SOCKET_TIMEOUT_MILLISECONDS = 3_000;
private final Logger logger = LoggerFactory.getLogger(Connection.class);
private @Nullable String ipAddress;
/**
* Initializes a connection to the given ip address.
*
* @param ipAddress ip address of the connection
*/
public Connection(@Nullable final String ipAddress) {
this.ipAddress = ipAddress;
}
/**
* Set the ip address to connect to.
*
* @param ipAddress The ip address to connect to
*/
public void setIpAddress(final String ipAddress) {
this.ipAddress = ipAddress;
}
/**
* Sends the command, which is a json string, encrypted to the device and decrypts the json result and returns it
*
* @param command json command to send to the device
* @return decrypted returned json result from the device
* @throws IOException exception in case device not reachable
*/
public synchronized String sendCommand(final String command) throws IOException {
logger.trace("Executing command: {}", command);
try (Socket socket = createSocket(); final OutputStream outputStream = socket.getOutputStream()) {
outputStream.write(CryptUtil.encryptWithLength(command));
final String response = readReturnValue(socket);
logger.trace("Command response: {}", response);
return response;
}
}
/**
* Reads and decrypts result returned from the device.
*
* @param socket socket to read result from
* @return decrypted result
* @throws IOException exception in case device not reachable
*/
private String readReturnValue(final Socket socket) throws IOException {
try (InputStream is = socket.getInputStream()) {
return CryptUtil.decryptWithLength(is);
}
}
/**
* Wrapper around socket creation to make mocking possible.
*
* @return new Socket instance
* @throws UnknownHostException exception in case the host could not be determined
* @throws IOException exception in case device not reachable
*/
protected Socket createSocket() throws UnknownHostException, IOException {
if (ipAddress == null) {
throw new IOException("Ip address not set. Wait for discovery or manually trigger discovery process.");
}
final Socket socket = new Socket(ipAddress, TP_LINK_SMART_HOME_PORT);
socket.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
return socket;
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Util class to encypt and decrypt data to be sent to and from the smart plug.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public final class CryptUtil {
private static final int KEY = 171;
private CryptUtil() {
// Util class
}
/**
* Decrypt the byte array of the specified length. The length is necessary because the byte array might be larger
* than the actual content.
*
* @param data byte array containing the data
* @param length number of bytes in the byte array to read
* @return decrypted String of the byte array
* @throws IOException exception in case device not reachable
*/
public static String decrypt(byte[] data, int length) throws IOException {
try (ByteArrayInputStream is = new ByteArrayInputStream(data)) {
return decrypt(is, length);
}
}
/**
* Decrypt the byte data in the input stream. In the first 4 bytes the length of the data in the byte array is
* coded.
*
* @param inputStream input stream containing length and data
* @return decrypted String of the inputstream
* @throws IOException exception in case device not reachable
*/
public static String decryptWithLength(InputStream inputStream) throws IOException {
try (DataInputStream is = new DataInputStream(inputStream)) {
return decrypt(is, is.readInt());
}
}
/**
* Decrypt the byte data in the input stream with the length as given.
*
* @param inputStream input stream
* @param length the number of bytes to read
* @return decrypted String of the inputstream
* @throws IOException exception in case device not reachable
*/
private static String decrypt(InputStream inputStream, int length) throws IOException {
final byte[] decryptedData = new byte[length];
int in;
int key = KEY;
int i = 0;
while (i < length && (in = inputStream.read()) != -1) {
final int nextKey = in;
decryptedData[i++] = (byte) (in ^ key);
key = nextKey;
}
return new String(decryptedData, StandardCharsets.UTF_8);
}
/**
* Encrypts the string into a byte array with the first for bytes specifying the length of the given String. The
* length is not encrypted.
*
* @param string String to encrypt
* @return byte array with length and encrypted string
*/
public static byte[] encryptWithLength(String string) {
ByteBuffer bb = ByteBuffer.allocate(4 + string.length());
bb.putInt(string.length());
bb.put(encrypt(string));
return bb.array();
}
/**
* Encrypts the string into a byte array.
*
* @param string String to encrypt
* @return byte array with encrypted string
*/
public static byte[] encrypt(String string) {
byte[] buffer = new byte[string.length()];
byte key = (byte) KEY;
for (int i = 0; i < string.length(); i++) {
buffer[i] = (byte) (string.charAt(i) ^ key);
key = buffer[i];
}
return buffer;
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.DeviceType;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo;
/**
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public final class PropertiesCollector {
private PropertiesCollector() {
// Util class
}
/**
* Collect all properties of the thing from the {@link Sysinfo} object.
*
* @param thingType thing to get the properties for
* @param ipAddress ip address of the device
* @param sysinfo system info data returned from the device
* @return map of properties
*/
public static Map<String, Object> collectProperties(TPLinkSmartHomeThingType thingType, String ipAddress,
Sysinfo sysinfo) {
final Map<String, Object> properties = new TreeMap<>();
putNonNull(properties, CONFIG_IP, ipAddress);
if (thingType.getDeviceType() == DeviceType.RANGE_EXTENDER) {
collectPropertiesRangeExtender(properties, sysinfo);
} else {
collectProperties(properties, sysinfo);
if (thingType.getDeviceType() == DeviceType.BULB) {
collectPropertiesBulb(properties, sysinfo);
} else {
collectPropertiesOther(properties, sysinfo);
}
}
return properties;
}
/**
* Collect generic properties.
*
* @param properties properties object to store properties in
* @param sysinfo system info data returned from the device
*/
private static void collectProperties(Map<String, Object> properties, Sysinfo sysinfo) {
putNonNull(properties, CONFIG_DEVICE_ID, sysinfo.getDeviceId());
putNonNull(properties, PROPERTY_MODEL, sysinfo.getModel());
putNonNull(properties, PROPERTY_HARDWARE_VERSION, sysinfo.getHwVer());
putNonNull(properties, PROPERTY_SOFWARE_VERSION, sysinfo.getSwVer());
putNonNull(properties, PROPERTY_HARDWARE_ID, sysinfo.getHwId());
putNonNull(properties, PROPERTY_OEM_ID, sysinfo.getOemId());
}
/**
* Collect Smart Bulb specific properties.
*
* @param properties properties object to store properties in
* @param sysinfo system info data returned from the device
*/
private static void collectPropertiesBulb(Map<String, Object> properties, Sysinfo sysinfo) {
putNonNull(properties, PROPERTY_TYPE, sysinfo.getType());
putNonNull(properties, PROPERTY_MAC, sysinfo.getMac());
putNonNull(properties, PROPERTY_PROTOCOL_NAME, sysinfo.getProtocolName());
putNonNull(properties, PROPERTY_PROTOCOL_VERSION, sysinfo.getProtocolVersion());
}
/**
* Collect Smart Range Extender specific properties.
*
* @param properties properties object to store properties in
* @param sysinfo system info data returned from the device
*/
private static void collectPropertiesRangeExtender(Map<String, Object> properties, Sysinfo sysinfo) {
final Sysinfo system = sysinfo.getSystem();
collectProperties(properties, system);
putNonNull(properties, PROPERTY_TYPE, system.getType());
putNonNull(properties, PROPERTY_MAC, system.getMac());
putNonNull(properties, PROPERTY_DEVICE_NAME, system.getDevName());
putNonNull(properties, PROPERTY_FEATURE, sysinfo.getPlug().getFeature());
}
/**
* Collect Smart Switch specific properties.
*
* @param properties properties object to store properties in
* @param sysinfo system info data returned from the device
*/
private static void collectPropertiesOther(Map<String, Object> properties, Sysinfo sysinfo) {
putNonNull(properties, PROPERTY_TYPE, sysinfo.getType());
putNonNull(properties, PROPERTY_MAC, sysinfo.getMac());
putNonNull(properties, PROPERTY_DEVICE_NAME, sysinfo.getDevName());
putNonNull(properties, PROPERTY_FIRMWARE_ID, sysinfo.getFwId());
putNonNull(properties, PROPERTY_FEATURE, sysinfo.getFeature());
}
private static void putNonNull(Map<String, Object> properties, String key, @Nullable String value) {
if (value != null) {
properties.put(key, value);
}
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Service to get the actual ip of a TP-Link Smart Home device as registered on the network.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public interface TPLinkIpAddressService {
/**
* Returns the last known ip address of the given device id. If no ip address known null is returned.
*
* @param deviceId id of the device to get the ip address of
* @return ip address or null
*/
@Nullable
String getLastKnownIpAddress(String deviceId);
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This class defines common constants, which are used across the whole binding.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Added channel and property keys
*/
@NonNullByDefault
public final class TPLinkSmartHomeBindingConstants {
public static final String BINDING_ID = "tplinksmarthome";
// List of all switch channel ids
public static final String CHANNEL_SWITCH = "switch";
// List of all plug channel ids
public static final String CHANNEL_LED = "led";
// List of all bulb channel ids
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_TEMPERATURE = "colorTemperature";
public static final String CHANNEL_COLOR_TEMPERATURE_ABS = "colorTemperatureAbs";
public static final int COLOR_TEMPERATURE_1_MIN = 2700;
public static final int COLOR_TEMPERATURE_1_MAX = 6500;
public static final int COLOR_TEMPERATURE_2_MIN = 2500;
public static final int COLOR_TEMPERATURE_2_MAX = 9000;
public static final Set<String> CHANNELS_BULB_SWITCH = Stream.of(CHANNEL_BRIGHTNESS, CHANNEL_COLOR,
CHANNEL_COLOR_TEMPERATURE, CHANNEL_COLOR_TEMPERATURE_ABS, CHANNEL_SWITCH).collect(Collectors.toSet());
// List of all energy channel ids
public static final String CHANNEL_ENERGY_POWER = "power";
public static final String CHANNEL_ENERGY_TOTAL = "energyUsage";
public static final String CHANNEL_ENERGY_VOLTAGE = "voltage";
public static final String CHANNEL_ENERGY_CURRENT = "current";
public static final Set<String> CHANNELS_ENERGY = Stream
.of(CHANNEL_ENERGY_POWER, CHANNEL_ENERGY_TOTAL, CHANNEL_ENERGY_VOLTAGE, CHANNEL_ENERGY_CURRENT)
.collect(Collectors.toSet());
// 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";
public static final String CONFIG_REFRESH = "refresh";
// Only for bulbs
public static final String CONFIG_TRANSITION_PERIOD = "transitionPeriod";
// List of property keys
public static final String PROPERTY_TYPE = "type";
public static final String PROPERTY_MODEL = "model";
public static final String PROPERTY_DEVICE_NAME = "device name";
public static final String PROPERTY_MAC = "mac";
public static final String PROPERTY_HARDWARE_VERSION = "hardware version";
public static final String PROPERTY_SOFWARE_VERSION = "sofware version";
public static final String PROPERTY_HARDWARE_ID = "hardware id";
public static final String PROPERTY_FIRMWARE_ID = "firmware id";
public static final String PROPERTY_OEM_ID = "oem id";
public static final String PROPERTY_FEATURE = "feature";
public static final String PROPERTY_PROTOCOL_NAME = "protocol name";
public static final String PROPERTY_PROTOCOL_VERSION = "protocol version";
public static final int FORCED_REFRESH_BOUNDERY_SECONDS = 60;
public static final int FORCED_REFRESH_BOUNDERY_SWITCHED_SECONDS = 5;
private TPLinkSmartHomeBindingConstants() {
// Constants class
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
/**
* Data class representing the user configurable settings of the device
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class TPLinkSmartHomeConfiguration {
/**
* IP Address of the device.
*/
public String ipAddress;
/**
* The id of the device;
*/
public String deviceId;
/**
* Refresh rate for the device in seconds.
*/
public int refresh;
/**
* Transition period of light bulb state changes in seconds.
*/
public int transitionPeriod;
}

View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.CONFIG_DEVICE_ID;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.*;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TPLinkSmartHomeDiscoveryService} detects new Smart Home Bulbs, Plugs and Switches by sending a UDP network
* broadcast and parsing the answer into a thing.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Complete make-over, reorganized code and code cleanup.
*/
@Component(service = { DiscoveryService.class,
TPLinkIpAddressService.class }, configurationPid = "discovery.tplinksmarthome")
@NonNullByDefault
public class TPLinkSmartHomeDiscoveryService extends AbstractDiscoveryService implements TPLinkIpAddressService {
private static final String BROADCAST_IP = "255.255.255.255";
private static final int DISCOVERY_TIMEOUT_SECONDS = 8;
private static final int UDP_PACKET_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(DISCOVERY_TIMEOUT_SECONDS - 1);
private static final long REFRESH_INTERVAL_MINUTES = 1;
private final Logger logger = LoggerFactory.getLogger(TPLinkSmartHomeDiscoveryService.class);
private final Commands commands = new Commands();
private final Map<String, String> idInetAddressCache = new ConcurrentHashMap<>();
private final DatagramPacket discoverPacket;
private final byte[] buffer = new byte[2048];
private @NonNullByDefault({}) DatagramSocket discoverSocket;
private @NonNullByDefault({}) ScheduledFuture<?> discoveryJob;
public TPLinkSmartHomeDiscoveryService() throws UnknownHostException {
super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SECONDS);
final InetAddress broadcast = InetAddress.getByName(BROADCAST_IP);
final byte[] discoverbuffer = CryptUtil.encrypt(Commands.getSysinfo());
discoverPacket = new DatagramPacket(discoverbuffer, discoverbuffer.length, broadcast,
Connection.TP_LINK_SMART_HOME_PORT);
}
@Override
public @Nullable String getLastKnownIpAddress(String deviceId) {
return idInetAddressCache.get(deviceId);
}
@Override
protected void startBackgroundDiscovery() {
discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, REFRESH_INTERVAL_MINUTES, TimeUnit.MINUTES);
}
@Override
protected void stopBackgroundDiscovery() {
stopScan();
if (discoveryJob != null && !discoveryJob.isCancelled()) {
discoveryJob.cancel(true);
discoveryJob = null;
}
}
@Override
protected void startScan() {
logger.debug("Start scan for TP-Link Smart devices.");
synchronized (this) {
try {
idInetAddressCache.clear();
discoverSocket = sendDiscoveryPacket();
// Runs until the socket call gets a time out and throws an exception. When a time out is triggered it
// means no data was present and nothing new to discover.
while (true) {
if (discoverSocket == null) {
break;
}
final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
discoverSocket.receive(packet);
logger.debug("TP-Link Smart device discovery returned package with length {}", packet.getLength());
if (packet.getLength() > 0) {
detectThing(packet);
}
}
} catch (SocketTimeoutException e) {
logger.debug("Discovering poller timeout...");
} catch (IOException e) {
logger.debug("Error during discovery: {}", e.getMessage());
} finally {
closeDiscoverSocket();
removeOlderResults(getTimestampOfLastScan());
}
}
}
@Override
protected void stopScan() {
logger.debug("Stop scan for TP-Link Smart devices.");
closeDiscoverSocket();
super.stopScan();
}
/**
* Opens a {@link DatagramSocket} and sends a packet for discovery of TP-Link Smart Home devices.
*
* @return Returns the new socket
* @throws IOException exception in case sending the packet failed
*/
protected DatagramSocket sendDiscoveryPacket() throws IOException {
final DatagramSocket ds = new DatagramSocket(null);
ds.setBroadcast(true);
ds.setSoTimeout(UDP_PACKET_TIMEOUT_MS);
ds.send(discoverPacket);
logger.trace("Discovery package sent.");
return ds;
}
/**
* Closes the discovery socket and cleans the value. No need for synchronization as this method is called from a
* synchronized context.
*/
private void closeDiscoverSocket() {
if (discoverSocket != null) {
discoverSocket.close();
discoverSocket = null;
}
}
/**
* Detected a device (thing) and get process the data from the device and report it discovered.
*
* @param packet containing data of detected device
* @throws IOException in case decrypting of the data failed
*/
private void detectThing(DatagramPacket packet) throws IOException {
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);
final String deviceId = sysinfo.getDeviceId();
logger.debug("TP-Link Smart Home device '{}' with id {} found on {} ", sysinfo.getAlias(), deviceId, ipAddress);
idInetAddressCache.put(deviceId, ipAddress);
final Optional<TPLinkSmartHomeThingType> thingType = getThingTypeUID(sysinfo.getModel());
if (thingType.isPresent()) {
final ThingTypeUID thingTypeUID = thingType.get().thingTypeUID();
final ThingUID thingUID = new ThingUID(thingTypeUID,
deviceId.substring(deviceId.length() - 6, deviceId.length()));
final Map<String, Object> properties = PropertiesCollector.collectProperties(thingType.get(), ipAddress,
sysinfoRaw);
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withLabel(sysinfo.getAlias()).withRepresentationProperty(CONFIG_DEVICE_ID)
.withProperties(properties).build();
thingDiscovered(discoveryResult);
} else {
logger.debug("Detected, but ignoring unsupported TP-Link Smart Home device model '{}'", sysinfo.getModel());
}
}
/**
* Finds the {@link ThingTypeUID} based on the model value returned by the device.
*
* @param model model value returned by the device
* @return {@link ThingTypeUID} or null if device not recognized
*/
private Optional<TPLinkSmartHomeThingType> getThingTypeUID(String model) {
final String modelLC = model.toLowerCase(Locale.ENGLISH);
return SUPPORTED_THING_TYPES_LIST.stream().filter(type -> modelLC.startsWith(type.thingTypeUID().getId()))
.findFirst();
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.device.BulbDevice;
import org.openhab.binding.tplinksmarthome.internal.device.DimmerDevice;
import org.openhab.binding.tplinksmarthome.internal.device.EnergySwitchDevice;
import org.openhab.binding.tplinksmarthome.internal.device.PowerStripDevice;
import org.openhab.binding.tplinksmarthome.internal.device.RangeExtenderDevice;
import org.openhab.binding.tplinksmarthome.internal.device.SmartHomeDevice;
import org.openhab.binding.tplinksmarthome.internal.device.SwitchDevice;
import org.openhab.binding.tplinksmarthome.internal.handler.SmartHomeHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link TPLinkSmartHomeHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Specific handlers for different type of devices.
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tplinksmarthome")
public class TPLinkSmartHomeHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) TPLinkIpAddressService ipAddressService;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Nullable
@Override
protected ThingHandler createHandler(Thing thing) {
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
final TPLinkSmartHomeThingType type = TPLinkSmartHomeThingType.THING_TYPE_MAP.get(thingTypeUID);
if (type == null) {
return null;
}
final SmartHomeDevice device;
switch (type.getDeviceType()) {
case BULB:
if (TPLinkSmartHomeThingType.isBulbDeviceWithTemperatureColor1(thingTypeUID)) {
device = new BulbDevice(thingTypeUID, COLOR_TEMPERATURE_1_MIN, COLOR_TEMPERATURE_1_MAX);
} else if (TPLinkSmartHomeThingType.isBulbDeviceWithTemperatureColor2(thingTypeUID)) {
device = new BulbDevice(thingTypeUID, COLOR_TEMPERATURE_2_MIN, COLOR_TEMPERATURE_2_MAX);
} else {
device = new BulbDevice(thingTypeUID);
}
break;
case DIMMER:
device = new DimmerDevice();
break;
case PLUG:
if (HS110.is(thingTypeUID)) {
device = new EnergySwitchDevice();
} else {
device = new SwitchDevice();
}
break;
case SWITCH:
device = new SwitchDevice();
break;
case RANGE_EXTENDER:
device = new RangeExtenderDevice();
break;
case STRIP:
device = new PowerStripDevice(type);
break;
default:
return null;
}
return new SmartHomeHandler(thing, device, type, ipAddressService);
}
@Reference
protected void setTPLinkIpAddressCache(TPLinkIpAddressService ipAddressCache) {
this.ipAddressService = ipAddressCache;
}
protected void unsetTPLinkIpAddressCache(TPLinkIpAddressService ipAddressCache) {
this.ipAddressService = null;
}
}

View File

@@ -0,0 +1,198 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* ThingType enum with all supported TP-Link Smart Home devices.
*
* @author Hilbrand Bouwkamp - Initial contribution
*
*/
@NonNullByDefault
public enum TPLinkSmartHomeThingType {
// Bulb Thing Type UIDs
KB100("kb100", DeviceType.BULB),
KB130("kb130", DeviceType.BULB),
LB100("lb100", DeviceType.BULB),
LB110("lb110", DeviceType.BULB),
LB120("lb120", DeviceType.BULB),
LB130("lb130", DeviceType.BULB),
LB200("lb200", DeviceType.BULB),
LB230("lb230", DeviceType.BULB),
KL50("kl50", DeviceType.BULB),
KL60("kl60", DeviceType.BULB),
KL110("kl110", DeviceType.BULB),
KL120("kl120", DeviceType.BULB),
KL130("kl130", DeviceType.BULB),
// Plug Thing Type UIDs
HS100("hs100", DeviceType.PLUG),
HS103("hs103", DeviceType.PLUG),
HS105("hs105", DeviceType.PLUG),
HS110("hs110", DeviceType.PLUG),
KP100("kp100", DeviceType.PLUG),
KP105("kp105", DeviceType.PLUG),
// Switch Thing Type UIDs
HS200("hs200", DeviceType.SWITCH),
HS210("hs210", DeviceType.SWITCH),
// Dimmer Thing Type UIDs
HS220("hs220", DeviceType.DIMMER),
// Power Strip Thing Type UIDs.
HS107("hs107", DeviceType.STRIP, 2),
HS300("hs300", DeviceType.STRIP, 6),
KP200("kp200", DeviceType.STRIP, 2),
KP303("kp303", DeviceType.STRIP, 3),
KP400("kp400", DeviceType.STRIP, 2),
// Range Extender Thing Type UIDs
RE270K("re270", DeviceType.RANGE_EXTENDER),
RE370K("re370", DeviceType.RANGE_EXTENDER);
/**
* All supported Smart Home devices in a list.
*/
public static final List<TPLinkSmartHomeThingType> SUPPORTED_THING_TYPES_LIST = Arrays
.asList(TPLinkSmartHomeThingType.values());
/**
* All {@link ThingTypeUID}s of all supported Smart Home devices.
*/
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = SUPPORTED_THING_TYPES_LIST.stream()
.map(TPLinkSmartHomeThingType::thingTypeUID).collect(Collectors.toSet());
/**
* A map of all {@link TPLinkSmartHomeThingType} mapped to {@link ThingTypeUID}.
*/
public static final Map<ThingTypeUID, TPLinkSmartHomeThingType> THING_TYPE_MAP = SUPPORTED_THING_TYPES_LIST.stream()
.collect(Collectors.toMap(TPLinkSmartHomeThingType::thingTypeUID, Function.identity()));
private static final List<TPLinkSmartHomeThingType> BULB_WITH_TEMPERATURE_COLOR_1 = Stream.of(LB120, KL120)
.collect(Collectors.toList());
private static final List<TPLinkSmartHomeThingType> BULB_WITH_TEMPERATURE_COLOR_2 = Stream
.of(KB130, KL130, LB130, LB230).collect(Collectors.toList());
private final ThingTypeUID thingTypeUID;
private final DeviceType type;
private final int sockets;
TPLinkSmartHomeThingType(final String name, final DeviceType type) {
this(name, type, 0);
}
TPLinkSmartHomeThingType(final String name, final DeviceType type, int sockets) {
thingTypeUID = new ThingTypeUID(TPLinkSmartHomeBindingConstants.BINDING_ID, name);
this.type = type;
this.sockets = sockets;
}
/**
* @return Returns the type of the device.
*/
public DeviceType getDeviceType() {
return type;
}
/**
* @return The {@link ThingTypeUID} of this device.
*/
public ThingTypeUID thingTypeUID() {
return thingTypeUID;
}
/**
* @return Returns the number of sockets. Only for Strip devices.
*/
public int getSockets() {
return sockets;
}
/**
* Returns true if the given {@link ThingTypeUID} matches a device that is a bulb with color temperature ranges 1
* (2700 to 6500k).
*
* @param thingTypeUID if the check
* @return true if it's a bulb device with color temperature range 1
*/
public static boolean isBulbDeviceWithTemperatureColor1(ThingTypeUID thingTypeUID) {
return isDevice(thingTypeUID, BULB_WITH_TEMPERATURE_COLOR_1);
}
/**
* Returns true if the given {@link ThingTypeUID} matches a device that is a bulb with color temperature ranges 2
* (2500 to 9000k).
*
* @param thingTypeUID if the check
* @return true if it's a bulb device with color temperature range 2
*/
public static boolean isBulbDeviceWithTemperatureColor2(ThingTypeUID thingTypeUID) {
return isDevice(thingTypeUID, BULB_WITH_TEMPERATURE_COLOR_2);
}
private static boolean isDevice(ThingTypeUID thingTypeUID, List<TPLinkSmartHomeThingType> thingTypes) {
return thingTypes.stream().anyMatch(t -> t.is(thingTypeUID));
}
/**
* Returns true if the given {@link ThingTypeUID} matches the {@link ThingTypeUID} in this enum.
*
* @param otherThingTypeUID to check
* @return true if matches
*/
public boolean is(ThingTypeUID otherThingTypeUID) {
return thingTypeUID.equals(otherThingTypeUID);
}
/**
* Enum indicating the type of the device.
*/
public enum DeviceType {
/**
* Light Bulb device.
*/
BULB,
/**
* Dimmer device.
*/
DIMMER,
/**
* Plug device.
*/
PLUG,
/**
* Wi-Fi range extender device with plug.
*/
RANGE_EXTENDER,
/**
* Power strip device.
*/
STRIP,
/**
* Switch device.
*/
SWITCH
}
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.binding.tplinksmarthome.internal.model.LightState;
import org.openhab.binding.tplinksmarthome.internal.model.TransitionLightStateResponse;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* TP-Link Smart Home Light Bulb.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class BulbDevice extends SmartHomeDevice {
protected Commands commands = new Commands();
private final int colorTempMin;
private final int colorTempMax;
private final int colorTempRangeFactor;
public BulbDevice(ThingTypeUID thingTypeUID) {
this(thingTypeUID, 0, 0);
}
public BulbDevice(ThingTypeUID thingTypeUID, int colorTempMin, int colorTempMax) {
this.colorTempMin = colorTempMin;
this.colorTempMax = colorTempMax;
colorTempRangeFactor = (colorTempMax - colorTempMin) / 100;
}
@Override
public String getUpdateCommand() {
return Commands.getRealtimeBulbAndSysinfo();
}
@Override
public boolean handleCommand(ChannelUID channelUid, Command command) throws IOException {
final String channelId = channelUid.getId();
final int transitionPeriod = configuration.transitionPeriod;
final HasErrorResponse response;
if (command instanceof OnOffType) {
response = handleOnOffType(channelId, (OnOffType) command, transitionPeriod);
} else if (command instanceof HSBType) {
response = handleHSBType(channelId, (HSBType) command, transitionPeriod);
} else if (command instanceof DecimalType) {
response = handleDecimalType(channelId, (DecimalType) command, transitionPeriod);
} else {
return false;
}
checkErrors(response);
return response != null;
}
private @Nullable HasErrorResponse handleOnOffType(String channelID, OnOffType onOff, int transitionPeriod)
throws IOException {
if (CHANNELS_BULB_SWITCH.contains(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setLightState(onOff, transitionPeriod)));
}
return null;
}
private @Nullable HasErrorResponse handleDecimalType(String channelID, DecimalType command, int transitionPeriod)
throws IOException {
if (CHANNEL_COLOR.equals(channelID) || CHANNEL_BRIGHTNESS.equals(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setBrightness(command.intValue(), transitionPeriod)));
} else if (CHANNEL_COLOR_TEMPERATURE.equals(channelID)) {
return handleColorTemperature(convertPercentageToKelvin(command.intValue()), transitionPeriod);
} else if (CHANNEL_COLOR_TEMPERATURE_ABS.equals(channelID)) {
return handleColorTemperature(guardColorTemperature(command.intValue()), transitionPeriod);
}
return null;
}
private @Nullable TransitionLightStateResponse handleColorTemperature(int colorTemperature, int transitionPeriod)
throws IOException {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setColorTemperature(colorTemperature, transitionPeriod)));
}
@Nullable
private HasErrorResponse handleHSBType(String channelID, HSBType command, int transitionPeriod) throws IOException {
if (CHANNEL_COLOR.equals(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setColor(command, transitionPeriod)));
}
return null;
}
@Override
public State updateChannel(ChannelUID channelUid, DeviceState deviceState) {
final LightState lightState = deviceState.getSysinfo().getLightState();
final State state;
switch (channelUid.getId()) {
case CHANNEL_BRIGHTNESS:
state = lightState.getBrightness();
break;
case CHANNEL_COLOR:
state = new HSBType(lightState.getHue(), lightState.getSaturation(), lightState.getBrightness());
break;
case CHANNEL_COLOR_TEMPERATURE:
state = new PercentType(convertKelvinToPercentage(lightState.getColorTemp()));
break;
case CHANNEL_COLOR_TEMPERATURE_ABS:
state = new DecimalType(guardColorTemperature(lightState.getColorTemp()));
break;
case CHANNEL_SWITCH:
state = lightState.getOnOff();
break;
case CHANNEL_ENERGY_POWER:
state = new DecimalType(deviceState.getRealtime().getPower());
break;
default:
state = UnDefType.UNDEF;
break;
}
return state;
}
private int convertPercentageToKelvin(int percentage) {
return guardColorTemperature(colorTempMin + colorTempRangeFactor * percentage);
}
private int convertKelvinToPercentage(int colorTemperature) {
return (guardColorTemperature(colorTemperature) - colorTempMin) / colorTempRangeFactor;
}
private int guardColorTemperature(int colorTemperature) {
return Math.max(colorTempMin, Math.min(colorTempMax, colorTemperature));
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.model.Realtime;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo;
/**
* Data class to store device state.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class DeviceState {
private final Commands commands = new Commands();
private final Realtime realtime;
private final Sysinfo sysinfo;
/**
* Initializes the device state given the json response from the device.
*
* @param state device state as json string
*/
public DeviceState(String state) {
sysinfo = commands.getSysinfoReponse(state);
realtime = commands.getRealtimeResponse(state);
}
/**
* @return returns the device energy data (if present)
*/
public Realtime getRealtime() {
return realtime;
}
/**
* @return returns the device state
*/
public Sysinfo getSysinfo() {
return sysinfo;
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.CHANNEL_BRIGHTNESS;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
/**
* TP-Link Smart Home device with a dimmer (HS220).
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class DimmerDevice extends SwitchDevice {
@Override
protected @Nullable HasErrorResponse setOnOffState(ChannelUID channelUid, OnOffType onOff) throws IOException {
return commands.setSwitchStateResponse(connection.sendCommand(commands.setSwitchState(onOff)));
}
@Override
public boolean handleCommand(ChannelUID channelUid, Command command) throws IOException {
return CHANNEL_BRIGHTNESS.equals(channelUid.getId()) ? handleBrightnessChannel(channelUid, command)
: super.handleCommand(channelUid, command);
}
/**
* Handle the brightness channel. Because the device has different commands for setting the device on/off and
* setting the brightness the on/off command must be send to the device as well when the brightness.
*
* @param channelUid uid of the channel to handle
* @param command command to the send
* @return returns true if the command was handled
* @throws IOException throws an {@link IOException} if the command handling failed
*/
private boolean handleBrightnessChannel(ChannelUID channelUid, Command command) throws IOException {
HasErrorResponse response = null;
if (command instanceof OnOffType) {
response = setOnOffState(channelUid, (OnOffType) command);
} else if (command instanceof PercentType) {
PercentType percentCommand = (PercentType) command;
// Don't send value 0 as brightness value as it will give an error from the device.
if (percentCommand.intValue() > 0) {
response = commands.setDimmerBrightnessResponse(
connection.sendCommand(commands.setDimmerBrightness(percentCommand.intValue())));
checkErrors(response);
if (response == null) {
return false;
}
response = setOnOffState(channelUid, OnOffType.ON);
} else {
response = setOnOffState(channelUid, OnOffType.OFF);
}
}
checkErrors(response);
return response != null;
}
@Override
public State updateChannel(ChannelUID channelUid, DeviceState deviceState) {
if (CHANNEL_BRIGHTNESS.equals(channelUid.getId())) {
return deviceState.getSysinfo().getRelayState() == OnOffType.OFF ? PercentType.ZERO
: new PercentType(deviceState.getSysinfo().getBrightness());
} else {
return super.updateChannel(channelUid, deviceState);
}
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.model.Realtime;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* TP-Link Smart Home Switch device that also can measure energy usage.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class EnergySwitchDevice extends SwitchDevice {
@Override
public String getUpdateCommand() {
return Commands.getRealtimeAndSysinfo();
}
@Override
public State updateChannel(ChannelUID channelUid, DeviceState deviceState) {
final State state;
final String matchChannelId = channelUid.isInGroup() ? channelUid.getIdWithoutGroup() : channelUid.getId();
if (CHANNELS_ENERGY.contains(matchChannelId)) {
state = updateEnergyChannel(matchChannelId, deviceState.getRealtime());
} else {
state = super.updateChannel(channelUid, deviceState);
}
return state;
}
/**
* Gets the state for an energy channel.
*
* @param channelId Id of the energy channel to get the state
* @param realtime data object containing the data from the device
* @return state object for the given channel
*/
protected State updateEnergyChannel(String channelId, Realtime realtime) {
final State value;
switch (channelId) {
case CHANNEL_ENERGY_CURRENT:
value = new QuantityType<>(realtime.getCurrent(), SmartHomeUnits.AMPERE);
break;
case CHANNEL_ENERGY_TOTAL:
value = new QuantityType<>(realtime.getTotal(), SmartHomeUnits.KILOWATT_HOUR);
break;
case CHANNEL_ENERGY_VOLTAGE:
value = new QuantityType<>(realtime.getVoltage(), SmartHomeUnits.VOLT);
break;
case CHANNEL_ENERGY_POWER:
value = new QuantityType<>(realtime.getPower(), SmartHomeUnits.WATT);
break;
default:
value = UnDefType.UNDEF;
break;
}
return value;
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType;
import org.openhab.binding.tplinksmarthome.internal.model.Realtime;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo.Outlet;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TP-Link Smart Home device with multiple sockets, like the HS107 and HS300.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class PowerStripDevice extends EnergySwitchDevice {
private final Logger logger = LoggerFactory.getLogger(PowerStripDevice.class);
private final List<@Nullable Realtime> realTimeCacheList;
private final List<@Nullable String> childIds;
public PowerStripDevice(final TPLinkSmartHomeThingType type) {
final int nrOfSockets = type.getSockets();
realTimeCacheList = new ArrayList<>(nrOfSockets);
childIds = new ArrayList<>(Collections.nCopies(nrOfSockets, ""));
}
@Override
public String getUpdateCommand() {
return Commands.getSysinfo();
}
@Override
public void refreshedDeviceState(@Nullable final DeviceState deviceState) {
if (deviceState != null) {
for (int i = 0; i < childIds.size(); i++) {
childIds.set(i, deviceState.getSysinfo().getChildren().get(i).getId());
realTimeCacheList.add(i, refreshCache(i));
}
}
}
@Override
protected State getOnOffState(final DeviceState deviceState) {
// Global On/Off state is determined by the combined state of all sockets. If 1 socket is on, the state is on.
return OnOffType
.from(deviceState.getSysinfo().getChildren().stream().anyMatch(e -> OnOffType.ON.equals(e.getState())));
}
@Override
public State updateChannel(final ChannelUID channelUid, final DeviceState deviceState) {
final int idx = channelToIndex(channelUid);
if (idx >= 0 && idx < childIds.size()) {
final Outlet outlet = deviceState.getSysinfo().getChildren().get(idx);
final String baseChannel = channelUid.getIdWithoutGroup();
if (CHANNEL_SWITCH.equals(baseChannel)) {
return outlet.getState();
} else if (CHANNELS_ENERGY.contains(baseChannel)) {
final Realtime realTime = realTimeCacheList.get(idx);
return realTime == null ? UnDefType.UNDEF : updateEnergyChannel(baseChannel, realTime);
}
} else {
if (idx >= 0) {
logger.debug("For channel update the index '{}' could be mapped to a channel. passed channel: {}", idx,
channelUid);
}
}
return super.updateChannel(channelUid, deviceState);
}
@Override
protected @Nullable String getChildId(final ChannelUID channelUid) {
final int idx = channelToIndex(channelUid);
return idx >= 0 && idx < childIds.size() ? childIds.get(idx) : null;
}
private int channelToIndex(final ChannelUID channelUid) {
final String groupId = channelUid.getGroupId();
return (groupId != null && groupId.startsWith(CHANNEL_OUTLET_GROUP_PREFIX)
? Integer.parseInt(groupId.substring(CHANNEL_OUTLET_GROUP_PREFIX.length()))
: 0) - 1;
}
private @Nullable Realtime refreshCache(final int idx) {
try {
final String childId = childIds.get(idx);
return childId == null ? null
: commands.getRealtimeResponse(connection.sendCommand(Commands.getRealtimeWithContext(childId)));
} catch (final IOException e) {
return null;
} catch (final RuntimeException e) {
logger.debug(
"Obtaining realtime data for channel-'{}' unexpectedly crashed. If this keeps happening please report: ",
idx, e);
return null;
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.model.SetRelayState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
/**
* TP-Link Smart Home range extender with smart plug.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class RangeExtenderDevice extends SwitchDevice {
@Override
protected State getOnOffState(DeviceState deviceState) {
return deviceState.getSysinfo().getPlug().getRelayStatus();
}
@Override
protected @Nullable SetRelayState setOnOffState(ChannelUID channelUid, OnOffType onOff) throws IOException {
// It's unknown what the command is to send to the device so it's not supported.
return null;
}
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.Connection;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeConfiguration;
import org.openhab.binding.tplinksmarthome.internal.model.ErrorResponse;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
/**
* Abstract class as base for Smart Home device implementations.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public abstract class SmartHomeDevice {
protected final Commands commands = new Commands();
protected @NonNullByDefault({}) Connection connection;
protected @NonNullByDefault({}) TPLinkSmartHomeConfiguration configuration;
/**
* Checks if the response object contains errors and if so throws an {@link IOException} when an error code was set.
*
* @param response The response to check for errors.
* @throws IOException if an error code was set in the response object
*/
protected void checkErrors(@Nullable HasErrorResponse response) throws IOException {
final ErrorResponse errorResponse = response == null ? null : response.getErrorResponse();
if (errorResponse != null && errorResponse.getErrorCode() != 0) {
throw new IOException("Error (" + errorResponse.getErrorCode() + "): " + errorResponse.getErrorMessage());
}
}
/**
* Sets connection and configuration values.
*
* @param connection The connection to the device
* @param configuration The global configuration
*/
public void initialize(Connection connection, TPLinkSmartHomeConfiguration configuration) {
this.connection = connection;
this.configuration = configuration;
}
/**
* @return the json string to send to the device to get the state of the device.
*/
public abstract String getUpdateCommand();
/**
* Handle the command for the given channel
*
* @param channelUID The channel the command is for
* @param command The command to be send to the device
* @return Returns true if the commands successfully was send to the device
* @throws IOException In case of communications error or the device returned an error
*/
public abstract boolean handleCommand(ChannelUID channelUID, Command command) throws IOException;
/**
* Returns the {@link State} value for the given value extracted from the deviceState data.
*
* @param channelUid channel to get state for
* @param deviceState state object containing the state
* @return {@link State} value for the given channel
*/
public abstract State updateChannel(ChannelUID channelUid, DeviceState deviceState);
/**
* Called with the new device state after the new device state is retrieved from the device.
*
* @param deviceState new device state
*/
public void refreshedDeviceState(@Nullable DeviceState deviceState) {
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* TP-Link Smart Home device with a switch, like Smart Plugs and Switches.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class SwitchDevice extends SmartHomeDevice {
@Override
public String getUpdateCommand() {
return Commands.getSysinfo();
}
@Override
public boolean handleCommand(ChannelUID channelUid, Command command) throws IOException {
return command instanceof OnOffType && handleOnOffType(channelUid, (OnOffType) command);
}
/**
* Returns the switch state.
*
* @param deviceState data object containing the state
* @return the switch state
*/
protected State getOnOffState(DeviceState deviceState) {
return deviceState.getSysinfo().getRelayState();
}
private boolean handleOnOffType(ChannelUID channelUid, OnOffType onOff) throws IOException {
final HasErrorResponse response;
final String baseChannelId = getBaseChannel(channelUid);
if (CHANNEL_SWITCH.contentEquals(baseChannelId)) {
response = setOnOffState(channelUid, onOff);
} else if (CHANNEL_LED.contentEquals(baseChannelId)) {
response = commands
.setLedOnResponse(connection.sendCommand(commands.setLedOn(onOff, getChildId(channelUid))));
} else {
response = null;
}
checkErrors(response);
return response != null;
}
/**
* Sends the {@link OnOffType} command to the device and returns the returned answer.
*
* @param channelUid channel Id to use to determine child id
* @param onOff command to the send
* @return state returned by the device
* @throws IOException exception in case device not reachable
*/
protected @Nullable HasErrorResponse setOnOffState(ChannelUID channelUid, OnOffType onOff) throws IOException {
return commands
.setRelayStateResponse(connection.sendCommand(commands.setRelayState(onOff, getChildId(channelUid))));
}
@Override
public State updateChannel(ChannelUID channelUid, DeviceState deviceState) {
final String baseChannelId = getBaseChannel(channelUid);
if (CHANNEL_SWITCH.equals(baseChannelId)) {
return getOnOffState(deviceState);
} else if (CHANNEL_LED.equals(baseChannelId)) {
return deviceState.getSysinfo().getLedOff();
}
return UnDefType.UNDEF;
}
/**
* Returns the child Id for the given channel if the device supports children and it's a channel for a specific
* child.
*
* @param channelUid channel Id to get the child id for
* @return null or child id
*/
protected @Nullable String getChildId(ChannelUID channelUid) {
return null;
}
private String getBaseChannel(ChannelUID channelUid) {
return channelUid.isInGroup() ? channelUid.getIdWithoutGroup() : channelUid.getId();
}
}

View File

@@ -0,0 +1,260 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.handler;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.util.StringUtil;
import org.openhab.binding.tplinksmarthome.internal.Connection;
import org.openhab.binding.tplinksmarthome.internal.TPLinkIpAddressService;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeConfiguration;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.DeviceType;
import org.openhab.binding.tplinksmarthome.internal.device.DeviceState;
import org.openhab.binding.tplinksmarthome.internal.device.SmartHomeDevice;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler class for TP-Link Smart Home devices.
*
* @author Christian Fischer - Initial contribution
* @author Hilbrand Bouwkamp - Rewrite to generic TP-Link Smart Home Handler
*/
@NonNullByDefault
public class SmartHomeHandler extends BaseThingHandler {
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
private final Logger logger = LoggerFactory.getLogger(SmartHomeHandler.class);
private final SmartHomeDevice smartHomeDevice;
private final TPLinkIpAddressService ipAddressService;
private final int forceRefreshThreshold;
private @NonNullByDefault({}) TPLinkSmartHomeConfiguration configuration;
private @NonNullByDefault({}) Connection connection;
private @NonNullByDefault({}) ScheduledFuture<?> refreshJob;
private @NonNullByDefault({}) ExpiringCache<@Nullable DeviceState> cache;
/**
* Cache to avoid refresh is called multiple time in 1 second.
*/
private @NonNullByDefault({}) ExpiringCache<@Nullable DeviceState> fastCache;
/**
* Constructor.
*
* @param thing The thing to handle
* @param smartHomeDevice Specific Smart Home device handler
* @param type The device type
* @param ipAddressService Cache keeping track of ip addresses of tp link devices
*/
public SmartHomeHandler(Thing thing, SmartHomeDevice smartHomeDevice, TPLinkSmartHomeThingType type,
TPLinkIpAddressService ipAddressService) {
super(thing);
this.smartHomeDevice = smartHomeDevice;
this.ipAddressService = ipAddressService;
this.forceRefreshThreshold = type.getDeviceType() == DeviceType.SWITCH
|| type.getDeviceType() == DeviceType.DIMMER ? FORCED_REFRESH_BOUNDERY_SWITCHED_SECONDS
: FORCED_REFRESH_BOUNDERY_SECONDS;
}
@Override
public void handleCommand(ChannelUID channelUid, Command command) {
try {
if (command instanceof RefreshType) {
updateChannelState(channelUid, fastCache.getValue());
} else if (smartHomeDevice.handleCommand(channelUid, command)) {
// After a command always refresh the cache to make sure the cache has the latest data
updateChannelState(channelUid, forceCacheUpdate());
} else {
logger.debug("Command {} is not supported for channel: {}", command, channelUid.getId());
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void dispose() {
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void initialize() {
configuration = getConfigAs(TPLinkSmartHomeConfiguration.class);
if (StringUtil.isBlank(configuration.ipAddress) && StringUtil.isBlank(configuration.deviceId)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No ip address or the device id configured.");
return;
}
logger.debug("Initializing TP-Link Smart device on ip '{}' or deviceId '{}' ", configuration.ipAddress,
configuration.deviceId);
connection = createConnection(configuration);
smartHomeDevice.initialize(connection, configuration);
cache = new ExpiringCache<>(Duration.ofSeconds(configuration.refresh), this::refreshCache);
// If refresh > threshold fast cache invalidates after 1 second, else it behaves just as the 'normal' cache
fastCache = configuration.refresh > forceRefreshThreshold
? new ExpiringCache<>(ONE_SECOND, this::forceCacheUpdate)
: cache;
updateStatus(ThingStatus.UNKNOWN);
// While config.xml defines refresh as min 1, this check is used to run a test that doesn't start refresh.
if (configuration.refresh > 0) {
startAutomaticRefresh(configuration);
}
}
/**
* Creates new Connection. Methods makes mocking of the connection in tests possible.
*
* @param config configuration to be used by the connection
* @return new Connection object
*/
Connection createConnection(TPLinkSmartHomeConfiguration config) {
return new Connection(config.ipAddress);
}
/**
* Invalidates the cache to force an update. It returns the refreshed cached value.
*
* @return the refreshed value
*/
private @Nullable DeviceState forceCacheUpdate() {
cache.invalidateValue();
return cache.getValue();
}
private @Nullable DeviceState refreshCache() {
try {
updateIpAddress();
final DeviceState deviceState = new DeviceState(connection.sendCommand(smartHomeDevice.getUpdateCommand()));
updateDeviceId(deviceState.getSysinfo().getDeviceId());
smartHomeDevice.refreshedDeviceState(deviceState);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
return deviceState;
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
return null;
} catch (RuntimeException e) {
logger.debug("Obtaining new device data unexpectedly crashed. If this keeps happening please report: ", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, e.getMessage());
return null;
}
}
/**
* Checks if the current configured ip addres is still the same as by which the device is registered on the network.
* If there is a different ip address for this device it will update the configuration with this ip and start using
* this ip address.
*/
private void updateIpAddress() {
if (configuration.deviceId == null) {
// The device id is needed to get the ip address so if not known no need to continue.
return;
}
String lastKnownIpAddress = ipAddressService.getLastKnownIpAddress(configuration.deviceId);
if (lastKnownIpAddress != null && !lastKnownIpAddress.equals(configuration.ipAddress)) {
Configuration editConfig = editConfiguration();
editConfig.put(CONFIG_IP, lastKnownIpAddress);
updateConfiguration(editConfig);
configuration.ipAddress = lastKnownIpAddress;
connection.setIpAddress(lastKnownIpAddress);
}
}
/**
* Updates the device id configuration if it's not set or throws an {@link IllegalArgumentException} if the
* configured device id doesn't match with the id reported by the device.
*
* @param actualDeviceId The id of the device as actual returned by the device.
* @throws IllegalArgumentException if the configured device id doesn't match with the id reported by the device
* itself.
*/
private void updateDeviceId(String actualDeviceId) {
if (StringUtil.isBlank(configuration.deviceId)) {
Configuration editConfig = editConfiguration();
editConfig.put(CONFIG_DEVICE_ID, actualDeviceId);
updateConfiguration(editConfig);
configuration.deviceId = actualDeviceId;
} else if (!StringUtil.isBlank(actualDeviceId) && !actualDeviceId.equals(configuration.deviceId)) {
throw new IllegalArgumentException(
String.format("The configured device '%s' doesn't match with the id the device reports: '%s'.",
configuration.deviceId, actualDeviceId));
}
}
/**
* Starts the background refresh thread.
*/
private void startAutomaticRefresh(TPLinkSmartHomeConfiguration config) {
if (refreshJob == null || refreshJob.isCancelled()) {
refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, config.refresh, config.refresh,
TimeUnit.SECONDS);
}
}
void refreshChannels() {
logger.trace("Update Channels for:{}", thing.getUID());
getThing().getChannels().forEach(channel -> updateChannelState(channel.getUID(), cache.getValue()));
}
/**
* Updates the state from the device data for the channel given the data..
*
* @param channelUID channel to update
* @param deviceState the state object containing the value to set of the channel
*
*/
private void updateChannelState(ChannelUID channelUID, @Nullable DeviceState deviceState) {
if (!isLinked(channelUID)) {
return;
}
String channelId = channelUID.isInGroup() ? channelUID.getIdWithoutGroup() : channelUID.getId();
final State state;
if (deviceState == null) {
state = UnDefType.UNDEF;
} else if (CHANNEL_RSSI.equals(channelId)) {
state = new QuantityType<>(deviceState.getSysinfo().getRssi(), SmartHomeUnits.DECIBEL_MILLIWATTS);
} else {
state = smartHomeDevice.updateChannel(channelUID, deviceState);
}
updateState(channelUID, state);
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import com.google.gson.annotations.Expose;
/**
* Class to be extended by state classes that support context. i.e. has multiple children that can be controlled.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class ContextState {
public static class Context {
@Expose
private String[] childIds = new String[1];
public void setChildId(String childId) {
this.childIds[0] = childId;
}
@Override
public String toString() {
return " child_ids:[" + childIds[0] + "]";
}
}
@Expose
private Context context;
public void setChildId(String childId) {
context = new Context();
context.setChildId(childId);
}
@Override
public String toString() {
return context == null ? "" : context.toString();
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import com.google.gson.annotations.Expose;
/**
* Base class for responses containing the common error response fields.
* Only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class ErrorResponse implements HasErrorResponse {
@Expose(serialize = false)
private int errCode;
@Expose(serialize = false)
private String errMsg;
/**
* @return the error code
*/
public int getErrorCode() {
return errCode;
}
/**
* @return the error message
*/
public String getErrorMessage() {
return errMsg;
}
@Override
public ErrorResponse getErrorResponse() {
return this;
}
@Override
public String toString() {
return "{err_code:" + errCode + ", err_msg:'" + errMsg + "'}";
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* Data class for getting the tp-Link Smart Plug energy state.
* Only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class GetRealtime extends ContextState {
public static class EMeter {
private Realtime getRealtime = new Realtime();
public Realtime getRealtime() {
return getRealtime;
}
@Override
public String toString() {
return "get_realtime:{" + getRealtime + "}";
}
}
@SerializedName(value = "emeter", alternate = "smartlife.iot.common.emeter")
private EMeter emeter = new EMeter();
public Realtime getRealtime() {
return emeter.getRealtime();
}
@Override
public String toString() {
return "GetRealtime {emeter:{" + emeter + "}}";
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Data class for getting the tp-Link Smart Plug state.
* Only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class GetSysinfo {
public static class System {
private Sysinfo getSysinfo = new Sysinfo();
public Sysinfo getGetSysinfo() {
return getSysinfo;
}
@Override
public String toString() {
return "get_sysinfo:{" + getSysinfo + "}";
}
}
private System system = new System();
public Sysinfo getSysinfo() {
return system.getGetSysinfo();
}
@Override
public String toString() {
return "GetSysinfo {system:{" + system + "}}";
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
/**
* Util class to create a {@link Gson} object.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public final class GsonUtil {
private GsonUtil() {
// Util class
}
/**
* Creates a new {@link Gson} object.
*
* @return new {@link Gson} object.
*/
public static Gson createGson() {
return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
}
/**
* Creates a new {@link Gson} object that uses the {@link Expose} annotation..
*
* @return new {@link Gson} object.
*/
public static Gson createGsonWithExpose() {
return new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
/**
* Interface for data objects that have an error code in their response.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@FunctionalInterface
public interface HasErrorResponse {
/**
* @return returns the object containing the error response
*/
ErrorResponse getErrorResponse();
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
/**
* Data class for reading the retrieved state of a Smart Home light bulb.
* Mostly only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class LightState extends ErrorResponse {
private int brightness;
private int colorTemp;
private int hue;
private int ignoreDefault;
private String mode;
private int onOff;
private int saturation;
public PercentType getBrightness() {
return onOff > 0 ? new PercentType(brightness) : PercentType.ZERO;
}
public int getColorTemp() {
return colorTemp;
}
public DecimalType getHue() {
return new DecimalType(hue);
}
public int getIgnoreDefault() {
return ignoreDefault;
}
public String getMode() {
return mode;
}
public OnOffType getOnOff() {
return onOff == 1 ? OnOffType.ON : OnOffType.OFF;
}
public PercentType getSaturation() {
return new PercentType(saturation);
}
public void setOnOff(OnOffType onOff) {
this.onOff = onOff == OnOffType.ON ? 1 : 0;
}
@Override
public String toString() {
return "brightness:" + brightness + ", color_temp:" + colorTemp + ", hue:" + hue + ", ignore_default:"
+ ignoreDefault + ", mode:" + mode + ", on_off:" + onOff + ", saturation:" + saturation
+ super.toString();
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
/**
* Data class for reading tp-Link Smart Plug energy monitoring.
* Only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class Realtime extends ErrorResponse {
private static final int MILLIWATT_TO_WATT = 1000;
private static final int MILLIAMP_TO_AMP = 1000;
private static final int WATTHOUR_TO_KILOWATTHOUR = 1000;
private static final int MILLIVOLT_TO_VOLT = 1000;
private double current;
private double power;
private double total;
private double voltage;
// JSON names used for v2 hardware
private double currentMa;
private double powerMw;
private double totalWh;
private double voltageMv;
public double getCurrent() {
return currentMa > 0.0 ? currentMa / MILLIAMP_TO_AMP : current;
}
public double getPower() {
return powerMw > 0.0 ? powerMw / MILLIWATT_TO_WATT : power;
}
public double getTotal() {
return totalWh > 0.0 ? totalWh / WATTHOUR_TO_KILOWATTHOUR : total;
}
public double getVoltage() {
return voltageMv > 0.0 ? voltageMv / MILLIVOLT_TO_VOLT : voltage;
}
@Override
public String toString() {
return "current:" + getCurrent() + ", power:" + getPower() + ", total:" + getTotal() + ", voltage:"
+ getVoltage() + super.toString();
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Data class to set the Brightness of a Smart Home dimmer (HS220).
* Setter methods for data and getter for error response.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class SetBrightness implements HasErrorResponse {
public static class Brightness extends ErrorResponse {
@Expose(deserialize = false)
private int brightness;
@Override
public String toString() {
return "brightness:" + brightness + super.toString();
}
}
public static class Dimmer {
@Expose
private Brightness setBrightness = new Brightness();
@Override
public String toString() {
return "set_brightness:{" + setBrightness + "}";
}
}
@Expose
@SerializedName("smartlife.iot.dimmer")
private Dimmer dimmer = new Dimmer();
@Override
public ErrorResponse getErrorResponse() {
return dimmer.setBrightness;
}
public void setBrightness(int brightness) {
dimmer.setBrightness.brightness = brightness;
}
@Override
public String toString() {
return "smartlife.iot.dimmer:{" + dimmer + "}";
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.annotations.Expose;
/**
* Data class to set the led of a Smart Home device on or off.
* Only setter methods as the object is only used to send values.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class SetLedOff extends ContextState implements HasErrorResponse {
public static class LedOff extends ErrorResponse {
@Expose
private int off;
public void setOff(int off) {
this.off = off;
}
@Override
public String toString() {
return "off:" + off + super.toString();
}
}
public static class System {
@Expose
private LedOff setLedOff = new LedOff();
@Override
public String toString() {
return "set_led_off:{" + setLedOff + "}";
}
}
@Expose
private System system = new System();
@Override
public ErrorResponse getErrorResponse() {
return system.setLedOff;
}
public void setLed(OnOffType onOff) {
system.setLedOff.setOff(onOff == OnOffType.OFF ? 1 : 0);
}
@Override
public String toString() {
return "SetLedOff {system:{" + system + "}";
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.annotations.Expose;
/**
* Data class for setting the TP-Link Smart Plug state and retrieving the result.
* Only setter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class SetRelayState extends ContextState implements HasErrorResponse {
public static class RelayState extends ErrorResponse {
@Expose(deserialize = false)
private int state;
@Override
public String toString() {
return "state:" + state;
}
}
public static class System {
@Expose
private RelayState setRelayState = new RelayState();
public void setRelayState(OnOffType onOff) {
setRelayState.state = onOff == OnOffType.ON ? 1 : 0;
}
@Override
public String toString() {
return "set_relay_state:{" + setRelayState + "}";
}
}
@Expose
private System system = new System();
@Override
public ErrorResponse getErrorResponse() {
return system.setRelayState;
}
public void setRelayState(OnOffType onOff) {
system.setRelayState(onOff);
}
@Override
public String toString() {
return "SetRelayState {system:{" + system + "}";
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Data class for setting the TP-Link Smart Dimmer (HS220) state and retrieving the result.
* Only setter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class SetSwitchState implements HasErrorResponse {
public static class State extends ErrorResponse {
@Expose(deserialize = false)
private int state;
@Override
public String toString() {
return "state:" + state;
}
}
public static class Dimmer {
@Expose
private State setSwitchState = new State();
@Override
public String toString() {
return "set_switch_state:{" + setSwitchState + "}";
}
}
@Expose
@SerializedName("smartlife.iot.dimmer")
private Dimmer dimmer = new Dimmer();
@Override
public ErrorResponse getErrorResponse() {
return dimmer.setSwitchState;
}
public void setSwitchState(OnOffType onOff) {
dimmer.setSwitchState.state = onOff == OnOffType.ON ? 1 : 0;
}
@Override
public String toString() {
return "SetSwitchState {smartlife.iot.dimmer:{" + dimmer + "}";
}
}

View File

@@ -0,0 +1,325 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.annotations.SerializedName;
/**
* Data class for reading TP-Link Smart Home device state.
* Only getter methods as the values are set by gson based on the retrieved json.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class Sysinfo extends ErrorResponse {
public static class CtrlProtocols {
private String name;
private String version;
public String getName() {
return name;
}
public String getVersion() {
return version;
}
@Override
public String toString() {
return "name:" + name + ", version:" + version;
}
}
/**
* With default light state state. The default light state is set when the device is off. If the device is on the
* state is in the parent fields.
*/
public static class WithDefaultLightState extends LightState {
private LightState dftOnState;
public LightState getLightState() {
if (dftOnState == null) {
return this;
} else {
dftOnState.setOnOff(getOnOff());
return dftOnState;
}
}
@Override
public String toString() {
return super.toString() + ", dftOnState:{" + dftOnState + "}";
}
}
/**
* Status of the plug as set in the range extender products.
*/
public static class Plug {
private String feature;
private String relayStatus;
public String getFeature() {
return feature;
}
public OnOffType getRelayStatus() {
return "ON".equals(relayStatus) ? OnOffType.ON : OnOffType.OFF;
}
}
/**
* Status of a single outlet on power strip.
*/
public static class Outlet {
private String alias;
private String id;
private long onTime;
private int state;
public String getAlias() {
return alias;
}
public String getId() {
return id;
}
public long getOnTime() {
return onTime;
}
public OnOffType getState() {
return state == 1 ? OnOffType.ON : OnOffType.OFF;
}
}
/**
* Status of the range extended Wi-Fi.
*/
public static class RangeextenderWireless {
private int w2gRssi;
public int getW2gRssi() {
return w2gRssi;
}
}
private String swVer;
private String hwVer;
private String model;
@SerializedName("deviceId")
private String deviceId;
@SerializedName("hwId")
private String hwId;
@SerializedName("oemId")
private String oemId;
private String alias;
private String activeMode;
private int rssi;
@SerializedName(value = "type", alternate = "mic_type")
private String type;
@SerializedName(value = "mac", alternate = { "mic_mac", "ethernet_mac" })
private String mac;
// switch and plug specific system info
@SerializedName("fwId")
private String fwId;
private String devName;
private String iconHash;
private int relayState; // 0 is off, 1 is on
private long onTime;
private String feature; // HS100 -> TIM, HS110 -> TIM:ENE
// Disabled updating as it's a different type for different devices.
// private int updating;
private int ledOff;
private double latitude;
private double longitude;
// powerstrip/multiple plugs support.
private int childNum;
private List<Outlet> children = new ArrayList<>();
// dimmer specific system info
private int brightness;
// bulb specific system info
private boolean isFactory;
private String discoVer;
private CtrlProtocols ctrlProtocols;
private WithDefaultLightState lightState = new WithDefaultLightState();
// range extender specific system info
private String ledStatus;
private Plug plug = new Plug();
private Sysinfo system;
@SerializedName("rangeextender.wireless")
private RangeextenderWireless reWireless;
public String getSwVer() {
return swVer;
}
public String getHwVer() {
return hwVer;
}
public String getType() {
return type;
}
public String getModel() {
return model;
}
public String getMac() {
return mac;
}
public String getDeviceId() {
return deviceId;
}
public String getHwId() {
return hwId;
}
public String getFwId() {
return fwId;
}
public String getOemId() {
return oemId;
}
public String getAlias() {
return alias;
}
public String getDevName() {
return devName;
}
public String getIconHash() {
return iconHash;
}
public OnOffType getRelayState() {
return relayState == 1 ? OnOffType.ON : OnOffType.OFF;
}
public int getBrightness() {
return brightness;
}
public long getOnTime() {
return onTime;
}
public String getActiveMode() {
return activeMode;
}
public String getFeature() {
return feature;
}
public int getRssi() {
// for range extender use the 2g rssi.
return reWireless == null ? rssi : reWireless.getW2gRssi();
}
public OnOffType getLedOff() {
return ledOff == 1 ? OnOffType.OFF : OnOffType.ON;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public boolean isFactory() {
return isFactory;
}
public String getDiscoVer() {
return discoVer;
}
public String getProtocolName() {
return ctrlProtocols == null ? null : ctrlProtocols.getName();
}
public String getProtocolVersion() {
return ctrlProtocols == null ? null : ctrlProtocols.getVersion();
}
public LightState getLightState() {
return lightState.getLightState();
}
public OnOffType getLedStatus() {
return "ON".equals(ledStatus) ? OnOffType.OFF : OnOffType.ON;
}
public Plug getPlug() {
return plug;
}
public int getChildNum() {
return childNum;
}
public List<Outlet> getChildren() {
return children;
}
public Sysinfo getSystem() {
return system;
}
/**
* Returns the {@link Sysinfo} object independent of the device. The range extender devices have the system
* information in another place as the other devices. This method returns the object independent of how the device
* returns it.
*
* @return device independent {@link Sysinfo} object.
*/
public Sysinfo getActualSysinfo() {
return system == null ? this : system;
}
public RangeextenderWireless getReWireless() {
return reWireless;
}
@Override
public String toString() {
return "Sysinfo [swVer=" + swVer + ", hwVer=" + hwVer + ", model=" + model + ", deviceId=" + deviceId
+ ", hwId=" + hwId + ", oemId=" + oemId + ", alias=" + alias + ", activeMode=" + activeMode + ", rssi="
+ rssi + ", type=" + type + ", mac=" + mac + ", fwId=" + fwId + ", devName=" + devName + ", iconHash="
+ iconHash + ", relayState=" + relayState + ", brightness=" + brightness + ", onTime=" + onTime
+ ", feature=" + feature + ", ledOff=" + ledOff + ", latitude=" + latitude + ", longitude=" + longitude
+ ", isFactory=" + isFactory + ", discoVer=" + discoVer + ", ctrlProtocols=" + ctrlProtocols
+ ", lightState=" + lightState + ", ledStatus=" + ledStatus + ", plug=" + plug + ", system=" + system
+ ", reWireless=" + reWireless + "]";
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
import com.google.gson.annotations.SerializedName;
/**
* Data class to setting different kind of states on Smart Home light bulbs.
* Only setter methods as the object is only used to send values.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class TransitionLightState {
/**
* Color sets hue, saturation and brightness.
* color temperature present to send with default 0 value to bulb.
*/
public static class LightStateColor extends LightStateBrightness {
private int colorTemp;
private int hue;
private int saturation;
public void setHue(int hue) {
this.hue = hue;
}
public void setSaturation(int saturation) {
this.saturation = saturation;
}
@Override
public String toString() {
return "colorTemp" + colorTemp + ", hue:" + hue + ", saturation:" + saturation + ", " + super.toString();
}
}
/**
* Color Temperature doesn't set brightness therefore separate class.
* hue and saturation present to send with default 0 value to bulb.
*/
public static class LightStateColorTemperature extends LightOnOff {
private int colorTemp;
private int hue;
private int saturation;
public void setColorTemperature(int colorTemp) {
this.colorTemp = colorTemp;
}
@Override
public String toString() {
return "colorTemp" + colorTemp + ", hue:" + hue + ", saturation:" + saturation + ", " + super.toString();
}
}
public static class LightStateBrightness extends LightOnOff {
private int brightness;
public void setBrightness(int brightness) {
this.brightness = brightness;
}
@Override
public String toString() {
return "brightness:" + brightness + ", " + super.toString();
}
}
public static class LightOnOff {
private int onOff;
private int ignoreDefault = 1;
private String mode = "normal";
private int transitionPeriod;
public void setOnOff(OnOffType onOff) {
this.onOff = onOff == OnOffType.ON ? 1 : 0;
}
public void setTransitionPeriod(int transitionPeriod) {
this.transitionPeriod = transitionPeriod;
}
@Override
public String toString() {
return "onOff:" + onOff + ", ignoreDefault:" + ignoreDefault + ", mode:" + mode + ", transitionPeriod:"
+ transitionPeriod;
}
}
public static class LightingService {
private LightOnOff transitionLightState;
@Override
public String toString() {
return "transitionLightState:{" + transitionLightState + "}";
}
}
@NonNullByDefault
@SerializedName("smartlife.iot.smartbulb.lightingservice")
private LightingService service = new LightingService();
public void setLightState(LightOnOff lightState) {
service.transitionLightState = lightState;
}
@Override
public String toString() {
return "TransitionLightState {service:{" + service + "}";
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import com.google.gson.annotations.SerializedName;
/**
* Data class for getting the response from the Light bulb. This class is similar to {@link TransitionLightState} but
* has no subclasses for the light state. The other class has and makes it difficult to use to deserialize by gson.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class TransitionLightStateResponse implements HasErrorResponse {
public static class LightingService {
private LightState transitionLightState;
@Override
public String toString() {
return "LightingService:{" + transitionLightState + "}";
}
}
@SerializedName("smartlife.iot.smartbulb.lightingservice")
private LightingService service = new LightingService();
@Override
public ErrorResponse getErrorResponse() {
return service.transitionLightState;
}
@Override
public String toString() {
return "TransitionLightStateResponse {service:{" + service + "}";
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="tplinksmarthome" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>TP-Link Smart Home Binding</name>
<description>This binding integrates the TP-Link Wi-Fi Smart Home devices.</description>
<author>Hilbrand Bouwkamp, Christian Fischer</author>
</binding:binding>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:device:bulb">
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP Address</label>
<description>IP Address of the device.</description>
</parameter>
<parameter name="deviceId" type="text">
<label>Device Id</label>
<description>The id of the device.</description>
</parameter>
<parameter name="transitionPeriod" type="integer" min="0">
<label>Transition Period</label>
<description>Time the transition to the new state takes in milliseconds.</description>
<unitLabel>milliseconds</unitLabel>
<default>0</default>
</parameter>
<parameter name="refresh" type="integer" min="1">
<label>Refresh Rate</label>
<description>Refresh of device state in seconds.</description>
<unitLabel>seconds</unitLabel>
<default>30</default>
</parameter>
</config-description>
<config-description uri="thing-type:device:plug">
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP Address</label>
<description>IP Address of the device.</description>
</parameter>
<parameter name="deviceId" type="text">
<label>Device Id</label>
<description>The id of the device.</description>
</parameter>
<parameter name="refresh" type="integer" min="1">
<label>Refresh Rate</label>
<description>Refresh of device state in seconds.</description>
<unitLabel>seconds</unitLabel>
<default>30</default>
</parameter>
</config-description>
<config-description uri="thing-type:device:switch">
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP Address</label>
<description>IP Address of the device.</description>
</parameter>
<parameter name="deviceId" type="text">
<label>Device Id</label>
<description>The id of the device.</description>
</parameter>
<parameter name="refresh" type="integer" min="1">
<label>Refresh Rate</label>
<description>Refresh of device state in seconds.</description>
<unitLabel>seconds</unitLabel>
<default>1</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs100">
<label>HS100</label>
<description>TP-Link HS100 Smart Wi-Fi Plug</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs103">
<label>HS103</label>
<description>TP-Link HS103 Smart Wi-Fi Plug Lite</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs105">
<label>HS105</label>
<description>TP-Link HS105 Smart Wi-Fi Plug</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs107">
<label>HS107</label>
<description>TP-Link HS107 Smart Wi-Fi Plug, 2-Outlets</description>
<category>PowerOutlet</category>
<channel-groups>
<channel-group id="groupSwitch" typeId="switch-group"/>
<channel-group id="outlet1" typeId="switch-outlet">
<label>Outlet 1</label>
</channel-group>
<channel-group id="outlet2" typeId="switch-outlet">
<label>Outlet 2</label>
</channel-group>
</channel-groups>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs110">
<label>HS110</label>
<description>TP-Link HS110 Smart Wi-Fi Plug with Energy Monitoring</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
<channel id="power" typeId="power"/>
<channel id="energyUsage" typeId="energy-usage"/>
<channel id="current" typeId="current"/>
<channel id="voltage" typeId="voltage"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs200">
<label>HS200</label>
<description>TP-Link HS200 Smart Wi-Fi Switch</description>
<category>WallSwitch</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:switch"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs210">
<label>HS210</label>
<description>TP-Link HS210 Smart Wi-Fi Light Switch 3-Way Kit</description>
<category>WallSwitch</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:switch"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs220">
<label>HS220</label>
<description>TP-Link HS220 Smart Wi-Fi Light Switch, Dimmer</description>
<category>WallSwitch</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:switch"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hs300">
<label>HS300</label>
<description>TP-Link HS300 Smart Wi-Fi Power Strip</description>
<category>PowerOutlet</category>
<channel-groups>
<channel-group id="group" typeId="switch-group"/>
<channel-group id="outlet1" typeId="energy-outlet">
<label>Outlet 1</label>
</channel-group>
<channel-group id="outlet2" typeId="energy-outlet">
<label>Outlet 2</label>
</channel-group>
<channel-group id="outlet3" typeId="energy-outlet">
<label>Outlet 3</label>
</channel-group>
<channel-group id="outlet4" typeId="energy-outlet">
<label>Outlet 4</label>
</channel-group>
<channel-group id="outlet5" typeId="energy-outlet">
<label>Outlet 5</label>
</channel-group>
<channel-group id="outlet6" typeId="energy-outlet">
<label>Outlet 6</label>
</channel-group>
</channel-groups>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kb100">
<label>KB100</label>
<description>TP-Link KB100 Kasa Smart Light Bulb</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kb130">
<label>KB130</label>
<description>TP-Link KB130 Kasa Multi-color Smart Light Bulb</description>
<category>Lightbulb</category>
<channels>
<channel id="color" typeId="system.color"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs2"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kl110">
<label>KL110</label>
<description>TP-Link KL110 Smart Wi-Fi LED Bulb with Brightness</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kl120">
<label>KL120</label>
<description>TP-Link KL120 Smart Wi-Fi LED Bulb with Tunable White Light Brightness</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs1"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kl130">
<label>KL130</label>
<description>TP-Link KL130 Smart Wi-Fi LED Bulb with Color Changing Hue</description>
<category>Lightbulb</category>
<channels>
<channel id="color" typeId="system.color"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs2"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kl50">
<label>KL50</label>
<description>Kasa Filament Smart Bulb, Soft White</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kl60">
<label>KL60</label>
<description>Kasa Filament Smart Bulb, Warm Amber</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kp100">
<label>KP100</label>
<description>TP-Link KP100 Kasa Wi-Fi Smart Plug - Slim Edition</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kp105">
<label>KP105</label>
<description>TP-Link KP105 Kasa Wi-Fi Smart Plug - Slim Edition</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kp200">
<label>KP200</label>
<description>TP-Link KP200 Smart Wi-Fi Power Outlet, 2-Sockets</description>
<category>PowerOutlet</category>
<channel-groups>
<channel-group id="groupSwitch" typeId="switch-group"/>
<channel-group id="outlet1" typeId="switch-outlet">
<label>Outlet 1</label>
</channel-group>
<channel-group id="outlet2" typeId="switch-outlet">
<label>Outlet 2</label>
</channel-group>
</channel-groups>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kp303">
<label>KP303</label>
<description>TP-Link HS300 Smart Wi-Fi Power Strip, 3-Outlets</description>
<category>PowerOutlet</category>
<channel-groups>
<channel-group id="group" typeId="switch-group"/>
<channel-group id="outlet1" typeId="switch-outlet">
<label>Outlet 1</label>
</channel-group>
<channel-group id="outlet2" typeId="switch-outlet">
<label>Outlet 2</label>
</channel-group>
<channel-group id="outlet3" typeId="switch-outlet">
<label>Outlet 3</label>
</channel-group>
</channel-groups>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="kp400">
<label>KP400</label>
<description>TP-Link KP400 Smart Outdoor Plug</description>
<category>PowerOutlet</category>
<channel-groups>
<channel-group id="groupSwitch" typeId="switch-group"/>
<channel-group id="outlet1" typeId="switch-outlet">
<label>Outlet 1</label>
</channel-group>
<channel-group id="outlet2" typeId="switch-outlet">
<label>Outlet 2</label>
</channel-group>
</channel-groups>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb100">
<label>LB100</label>
<description>TP-Link LB100 Smart Wi-Fi LED Bulb with Dimmable Light</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb110">
<label>LB110</label>
<description>TP-Link LB110 Smart Wi-Fi LED Bulb with Dimmable Light</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb120">
<label>LB120</label>
<description>TP-Link LB120 Smart Wi-Fi LED Bulb with Tunable White Light</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs1"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb130">
<label>LB130</label>
<description>TP-Link LB130 Smart Wi-Fi LED Bulb with Color Changing Hue</description>
<category>Lightbulb</category>
<channels>
<channel id="color" typeId="system.color"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs2"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb200">
<label>LB200</label>
<description>TP-Link LB200 Smart Wi-Fi LED Bulb with Dimmable Light</description>
<category>Lightbulb</category>
<channels>
<channel id="brightness" typeId="system.brightness"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="lb230">
<label>LB230</label>
<description>TP-Link LB230 Smart Wi-Fi LED Bulb with Color Changing Hue</description>
<category>Lightbulb</category>
<channels>
<channel id="color" typeId="system.color"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs2"/>
<channel id="power" typeId="power"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="re270">
<label>RE270K</label>
<description>TP-Link AC750 Wi-Fi Range Extender with Smart Plug</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="switch-readonly"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="re370">
<label>RE370K</label>
<description>TP-Link AC1200 Wi-Fi Range Extender with Smart Plug</description>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="switch-readonly"/>
<channel id="rssi" typeId="rssi"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description-ref uri="thing-type:device:plug"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tplinksmarthome"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Primary Channel types -->
<channel-type id="switch-readonly">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Shows the switch state of the Smart Home device.</description>
<category>Switch</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="led" advanced="true">
<item-type>Switch</item-type>
<label>Switch Led</label>
<description>Switch the Smart Home device led on or off.</description>
<category>Switch</category>
</channel-type>
<!-- Absolute Temperature Channel -->
<channel-type id="colorTemperatureAbs1" advanced="true">
<item-type>Number</item-type>
<label>Color Temperature</label>
<description>This channel supports adjusting the color temperature from 2700K to 6500K.</description>
<category>ColorLight</category>
<state min="2700" max="6500" pattern="%d K"/>
</channel-type>
<channel-type id="colorTemperatureAbs2" advanced="true">
<item-type>Number</item-type>
<label>Color Temperature</label>
<description>This channel supports adjusting the color temperature from 2500K to 9000K.</description>
<category>ColorLight</category>
<state min="2500" max="9000" pattern="%d K"/>
</channel-type>
<!-- Energy Channel types -->
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Actual power usage.</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="energy-usage">
<item-type>Number:Energy</item-type>
<label>Energy Usage</label>
<description>Actual energy usage.</description>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"></state>
</channel-type>
<channel-type id="current" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Current</label>
<description>Actual current usage.</description>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"></state>
</channel-type>
<channel-type id="voltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Actual voltage usage.</description>
<category>Energy</category>
<state readOnly="true" pattern="%.0f %unit%"></state>
</channel-type>
<!-- Misc Channel types -->
<channel-type id="rssi" advanced="true">
<item-type>Number:Power</item-type>
<label>Signal</label>
<description>Wi-Fi signal strength indicator.</description>
<category>QualityOfService</category>
<state readOnly="true" pattern="%d %unit%"></state>
</channel-type>
<!-- Channel Groups types -->
<channel-group-type id="switch-group">
<label>Outlet Group</label>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="led" typeId="led"/>
<channel id="rssi" typeId="rssi"/>
</channels>
</channel-group-type>
<channel-group-type id="switch-outlet">
<label>Outlet</label>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
</channels>
</channel-group-type>
<channel-group-type id="energy-outlet">
<label>Outlet</label>
<category>PowerOutlet</category>
<channels>
<channel id="switch" typeId="system.power"/>
<channel id="power" typeId="power"/>
<channel id="energyUsage" typeId="energy-usage"/>
<channel id="current" typeId="current"/>
<channel id="voltage" typeId="voltage"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
/**
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public final class ChannelUIDConstants {
public static final ChannelUID CHANNEL_UID_BRIGHTNESS = createChannel(LB130, CHANNEL_BRIGHTNESS);
public static final ChannelUID CHANNEL_UID_COLOR = createChannel(LB130, CHANNEL_COLOR);
public static final ChannelUID CHANNEL_UID_COLOR_TEMPERATURE = createChannel(LB130, CHANNEL_COLOR_TEMPERATURE);
public static final ChannelUID CHANNEL_UID_COLOR_TEMPERATURE_ABS = createChannel(LB130,
CHANNEL_COLOR_TEMPERATURE_ABS);
public static final ChannelUID CHANNEL_UID_ENERGY_CURRENT = createChannel(HS110, CHANNEL_ENERGY_CURRENT);
public static final ChannelUID CHANNEL_UID_ENERGY_POWER = createChannel(HS110, CHANNEL_ENERGY_POWER);
public static final ChannelUID CHANNEL_UID_ENERGY_TOTAL = createChannel(HS110, CHANNEL_ENERGY_TOTAL);
public static final ChannelUID CHANNEL_UID_ENERGY_VOLTAGE = createChannel(HS110, CHANNEL_ENERGY_VOLTAGE);
public static final ChannelUID CHANNEL_UID_LED = createChannel(HS100, CHANNEL_LED);
public static final ChannelUID CHANNEL_UID_OTHER = createChannel(HS100, "OTHER");
public static final ChannelUID CHANNEL_UID_RSSI = createChannel(HS100, CHANNEL_RSSI);
public static final ChannelUID CHANNEL_UID_SWITCH = createChannel(HS100, CHANNEL_SWITCH);
private static final String ID = "1234";
private ChannelUIDConstants() {
// Util class
}
private static ChannelUID createChannel(TPLinkSmartHomeThingType thingType, String channelId) {
return new ChannelUID(new ThingUID(thingType.thingTypeUID(), ID), channelId);
}
public static ChannelUID createChannel(TPLinkSmartHomeThingType thingType, String groupId, String channelId) {
return new ChannelUID(new ThingUID(thingType.thingTypeUID(), ID), groupId, channelId);
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.junit.Test;
/**
* Test class for {@link CryptUtil} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class CryptUtilTest {
private static final String TEST_STRING = "This is just a message";
/**
* Test round trip of encrypt and decrypt that should return the same value.
*
* @throws IOException exception in case device not reachable
*/
@Test
public void testCrypt() throws IOException {
assertEquals("Crypting should result in same string", TEST_STRING,
CryptUtil.decrypt(CryptUtil.encrypt(TEST_STRING), TEST_STRING.length()));
}
/**
* Test round trip of encrypt and decrypt with length that should return the same value.
*
* @throws IOException exception in case device not reachable
*/
@Test
public void testCryptWithLength() throws IOException {
try (final ByteArrayInputStream is = new ByteArrayInputStream(CryptUtil.encryptWithLength(TEST_STRING))) {
assertEquals("Crypting should result in same string", TEST_STRING, CryptUtil.decryptWithLength(is));
}
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.binding.tplinksmarthome.internal.model.GetSysinfo;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
/**
* Test class for {@link PropertiesCollector} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class PropertiesCollectorTest {
/**
* Tests if properties for a bulb device are correctly parsed.
*
* @throws IOException exception in case device not reachable
*/
@Test
public void testBulbProperties() throws IOException {
assertProperties("bulb_get_sysinfo_response_on", TPLinkSmartHomeThingType.LB130, 11);
}
/**
* Tests if properties for a switch device are correctly parsed.
*
* @throws IOException exception in case device not reachable
*/
@Test
public void testSwitchProperties() throws IOException {
assertProperties("plug_get_sysinfo_response", TPLinkSmartHomeThingType.HS100, 12);
}
/**
* Tests if properties for a range extender device are correctly parsed.
*
* @throws IOException exception in case device not reachable
*/
@Test
public void testRangeExtenderProperties() throws IOException {
assertProperties("rangeextender_get_sysinfo_response", TPLinkSmartHomeThingType.RE270K, 11);
}
private void assertProperties(String responseFile, TPLinkSmartHomeThingType thingType, int expectedSize)
throws IOException {
final Map<String, Object> props = PropertiesCollector.collectProperties(thingType, "localhost",
ModelTestUtil.jsonFromFile(responseFile, GetSysinfo.class).getSysinfo());
assertEquals("Number of properties not as expected for properties: " + props, expectedSize, props.size());
props.entrySet().stream().forEach(
entry -> assertNotNull("Property '" + entry.getKey() + "' should not be null", entry.getValue()));
}
}

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult;
/**
* Test class for {@link TPLinkSmartHomeDiscoveryService} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@RunWith(value = Parameterized.class)
public class TPLinkSmartHomeDiscoveryServiceTest {
private static final List<Object[]> TESTS = Arrays.asList(
new Object[][] { { "bulb_get_sysinfo_response_on", 11 }, { "rangeextender_get_sysinfo_response", 11 } });
@Mock
private DatagramSocket discoverSocket;
@Mock
private DiscoveryListener discoveryListener;
private TPLinkSmartHomeDiscoveryService discoveryService;
private final String filename;
private final int propertiesSize;
public TPLinkSmartHomeDiscoveryServiceTest(String filename, int propertiesSize) {
this.filename = filename;
this.propertiesSize = propertiesSize;
}
@Parameters(name = "{0}")
public static List<Object[]> data() {
return TESTS;
}
@Before
public void setUp() throws IOException {
initMocks(this);
discoveryService = new TPLinkSmartHomeDiscoveryService() {
@Override
protected DatagramSocket sendDiscoveryPacket() throws IOException {
return discoverSocket;
}
};
doAnswer(new Answer<Void>() {
private int cnt;
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (cnt++ > 0) {
throw new SocketTimeoutException("Only test 1 thing discovery");
}
DatagramPacket packet = (DatagramPacket) invocation.getArguments()[0];
packet.setAddress(InetAddress.getLocalHost());
packet.setData(CryptUtil.encrypt(ModelTestUtil.readJson(filename)));
return null;
}
}).when(discoverSocket).receive(any());
discoveryService.addDiscoveryListener(discoveryListener);
}
/**
* Test if startScan method finds a device with expected properties.
*/
@Test
public void testScan() {
discoveryService.startScan();
ArgumentCaptor<DiscoveryResult> discoveryResultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(discoveryListener).thingDiscovered(any(), discoveryResultCaptor.capture());
DiscoveryResult discoveryResult = discoveryResultCaptor.getValue();
assertEquals("Check if correct binding id found", TPLinkSmartHomeBindingConstants.BINDING_ID,
discoveryResult.getBindingId());
assertEquals("Check if expected number of properties found", propertiesSize,
discoveryResult.getProperties().size());
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.Mock;
import org.openhab.binding.tplinksmarthome.internal.device.BulbDevice;
import org.openhab.binding.tplinksmarthome.internal.device.DimmerDevice;
import org.openhab.binding.tplinksmarthome.internal.device.EnergySwitchDevice;
import org.openhab.binding.tplinksmarthome.internal.device.PowerStripDevice;
import org.openhab.binding.tplinksmarthome.internal.device.RangeExtenderDevice;
import org.openhab.binding.tplinksmarthome.internal.device.SwitchDevice;
import org.openhab.binding.tplinksmarthome.internal.handler.SmartHomeHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
/**
* Test class for {@link TPLinkSmartHomeHandlerFactory}.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@RunWith(value = Parameterized.class)
public class TPLinkSmartHomeHandlerFactoryTest {
private static final String SMART_HOME_DEVICE_FIELD = "smartHomeDevice";
private final TPLinkSmartHomeHandlerFactory factory = new TPLinkSmartHomeHandlerFactory();
// @formatter:off
private static final List<Object[]> TESTS = Arrays.asList(new Object[][] {
{ "hs100", SwitchDevice.class },
{ "hs110", EnergySwitchDevice.class },
{ "hs200", SwitchDevice.class },
{ "hs220", DimmerDevice.class },
{ "hs300", PowerStripDevice.class },
{ "lb100", BulbDevice.class },
{ "lb120", BulbDevice.class },
{ "lb130", BulbDevice.class },
{ "lb230", BulbDevice.class },
{ "kl110", BulbDevice.class },
{ "kl120", BulbDevice.class },
{ "kl130", BulbDevice.class },
{ "re270", RangeExtenderDevice.class },
{ "unknown", null },
});
// @formatter:on
@Mock
Thing thing;
private final String name;
private final Class<?> clazz;
public TPLinkSmartHomeHandlerFactoryTest(String name, Class<?> clazz) {
this.name = name;
this.clazz = clazz;
}
@Parameters(name = "{0} - {1}")
public static List<Object[]> data() {
return TESTS;
}
@Before
public void setUp() {
initMocks(this);
}
@Test
public void testCorrectClass()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
when(thing.getThingTypeUID()).thenReturn(new ThingTypeUID(TPLinkSmartHomeBindingConstants.BINDING_ID, name));
SmartHomeHandler handler = (SmartHomeHandler) factory.createHandler(thing);
if (clazz == null) {
assertNull(name + " should not return any handler but null", handler);
} else {
assertNotNull(name + " should no return null handler", handler);
Field smartHomeDeviceField = SmartHomeHandler.class.getDeclaredField(SMART_HOME_DEVICE_FIELD);
smartHomeDeviceField.setAccessible(true);
assertSame(name + " should return expected device class", clazz,
smartHomeDeviceField.get(handler).getClass());
}
}
}

View File

@@ -0,0 +1,171 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.LB130;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link BulbDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class BulbDeviceTest extends DeviceTestBase<BulbDevice> {
private static final String DEVICE_OFF = "bulb_get_sysinfo_response_off";
public BulbDeviceTest() throws IOException {
super(new BulbDevice(LB130.thingTypeUID(), COLOR_TEMPERATURE_2_MIN, COLOR_TEMPERATURE_2_MAX),
"bulb_get_sysinfo_response_on");
}
@Override
public void setUp() throws IOException {
super.setUp();
setSocketReturnAssert("bulb_transition_light_state_response");
}
@Test
public void testHandleCommandBrightness() throws IOException {
assertInput("bulb_transition_light_state_brightness");
assertTrue("Brightness channel should be handled",
device.handleCommand(CHANNEL_UID_BRIGHTNESS, new PercentType(33)));
}
@Test
public void testHandleCommandBrightnessOnOff() throws IOException {
assertInput("bulb_transition_light_state_on");
assertTrue("Brightness channel with OnOff state should be handled",
device.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON));
}
@Test
public void testHandleCommandColor() throws IOException {
assertInput("bulb_transition_light_state_color");
assertTrue("Color channel should be handled", device.handleCommand(CHANNEL_UID_COLOR, new HSBType("55,44,33")));
}
public void testHandleCommandColorBrightness() throws IOException {
assertInput("bulb_transition_light_state_brightness");
assertTrue("Color channel with Percentage state (=brightness) should be handled",
device.handleCommand(CHANNEL_UID_COLOR, new PercentType(33)));
}
public void testHandleCommandColorOnOff() throws IOException {
assertInput("bulb_transition_light_state_on");
assertTrue("Color channel with OnOff state should be handled",
device.handleCommand(CHANNEL_UID_COLOR, OnOffType.ON));
}
@Test
public void testHandleCommandColorTemperature() throws IOException {
assertInput("bulb_transition_light_state_color_temp");
assertTrue("Color temperature channel should be handled",
device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE, new PercentType(40)));
}
@Test
public void testHandleCommandColorTemperatureAbs() throws IOException {
assertInput("bulb_transition_light_state_color_temp");
assertTrue("Color temperature channel should be handled",
device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE_ABS, new DecimalType(5100)));
}
@Test
public void testHandleCommandColorTemperatureOnOff() throws IOException {
assertInput("bulb_transition_light_state_on");
assertTrue("Color temperature channel with OnOff state should be handled",
device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE, OnOffType.ON));
}
@Test
public void testHandleCommandSwitch() throws IOException {
assertInput("bulb_transition_light_state_on");
assertTrue("Switch channel should be handled", device.handleCommand(CHANNEL_UID_SWITCH, OnOffType.ON));
}
@Test
public void testUpdateChannelBrightnessOn() {
assertEquals("Brightness should be on", new PercentType(92),
device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState));
}
@Test
public void testUpdateChannelBrightnessOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertEquals("Brightness should be off", PercentType.ZERO,
device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState));
}
@Test
public void testUpdateChannelColorOn() {
assertEquals("Color should be on", new HSBType("7,44,92"),
device.updateChannel(CHANNEL_UID_COLOR, deviceState));
}
@Test
public void testUpdateChannelColorOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertEquals("Color should be off", new HSBType("7,44,0"),
device.updateChannel(CHANNEL_UID_COLOR, deviceState));
}
@Test
public void testUpdateChannelSwitchOn() {
assertSame("Switch should be on", OnOffType.ON, device.updateChannel(CHANNEL_UID_SWITCH, deviceState));
}
@Test
public void testUpdateChannelSwitchOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertSame("Switch should be off", OnOffType.OFF, device.updateChannel(CHANNEL_UID_SWITCH, deviceState));
}
@Test
public void testUpdateChannelColorTemperature() {
assertEquals("Color temperature should be set", new PercentType(2),
device.updateChannel(CHANNEL_UID_COLOR_TEMPERATURE, deviceState));
}
@Test
public void testUpdateChannelColorTemperatureAbs() {
assertEquals("Color temperature should be set", new DecimalType(2630),
device.updateChannel(CHANNEL_UID_COLOR_TEMPERATURE_ABS, deviceState));
}
@Test
public void testUpdateChannelOther() {
assertSame("Unknown channel should return UNDEF", UnDefType.UNDEF,
device.updateChannel(CHANNEL_UID_OTHER, deviceState));
}
@Test
public void testUpdateChannelPower() {
assertEquals("Power values should be set", new DecimalType(10.8),
device.updateChannel(CHANNEL_UID_ENERGY_POWER, deviceState));
}
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Before;
import org.mockito.Mock;
import org.openhab.binding.tplinksmarthome.internal.Connection;
import org.openhab.binding.tplinksmarthome.internal.CryptUtil;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeConfiguration;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
/**
* Base class for tests that test classes extending {@link SmartHomeDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class DeviceTestBase<T extends SmartHomeDevice> {
protected final T device;
protected final Connection connection;
protected final TPLinkSmartHomeConfiguration configuration = new TPLinkSmartHomeConfiguration();
protected @NonNullByDefault({}) DeviceState deviceState;
private final String deviceStateFilename;
@Mock
private @NonNullByDefault({}) Socket socket;
@Mock
private @NonNullByDefault({}) OutputStream outputStream;
/**
* Constructor.
*
* @param device Device under test
* @param deviceStateFilename name of the file to read the device state json from to use in tests
*
* @throws IOException exception in case device not reachable
*/
protected DeviceTestBase(T device, String deviceStateFilename) throws IOException {
this.device = device;
this.deviceStateFilename = deviceStateFilename;
configuration.ipAddress = "localhost";
configuration.refresh = 30;
configuration.transitionPeriod = 10;
connection = new Connection(configuration.ipAddress) {
@Override
protected Socket createSocket() throws IOException {
return socket;
}
};
device.initialize(connection, configuration);
}
@Before
public void setUp() throws IOException {
initMocks(this);
when(socket.getOutputStream()).thenReturn(outputStream);
deviceState = new DeviceState(ModelTestUtil.readJson(deviceStateFilename));
}
/**
* Sets the answer to return when the socket.getInputStream() is requested. If multiple files are given they will
* returned in order each time a call to socket.getInputStream() is done.
*
* @param responseFilenames names of the files to read that contains the answer. It's the unencrypted json string
* @throws IOException exception in case device not reachable
*/
protected void setSocketReturnAssert(String... responseFilenames) throws IOException {
AtomicInteger index = new AtomicInteger();
doAnswer(i -> {
String stateResponse = ModelTestUtil.readJson(responseFilenames[index.getAndIncrement()]);
return new ByteArrayInputStream(CryptUtil.encryptWithLength(stateResponse));
}).when(socket).getInputStream();
}
/**
* Asserts the value passed to outputstream.write, which is the call that would be made to the actual device. This
* checks if the value sent to the device is what is expected to be sent to the device. If multiple files are given
* they will be used to check in order each time a call outputstream.write is done.
*
* @param filenames names of the files containing the reference json
* @throws IOException exception in case device not reachable
*/
protected void assertInput(String... filenames) throws IOException {
assertInput(Function.identity(), Function.identity(), filenames);
}
protected void assertInput(Function<String, String> jsonProcessor, Function<String, String> expectedProcessor,
String... filenames) throws IOException {
AtomicInteger index = new AtomicInteger();
doAnswer(arg -> {
String json = jsonProcessor.apply(ModelTestUtil.readJson(filenames[index.get()]));
byte[] input = (byte[]) arg.getArguments()[0];
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(input)) {
String expectedString = expectedProcessor.apply(CryptUtil.decryptWithLength(inputStream));
assertEquals(filenames[index.get()], json, expectedString);
}
index.incrementAndGet();
return null;
}).when(outputStream).write(any());
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link DimmerDevice}.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class DimmerDeviceTest extends DeviceTestBase<DimmerDevice> {
private static final PercentType BRIGHTNESS_VALUE = new PercentType(50);
public DimmerDeviceTest() throws IOException {
super(new DimmerDevice(), "hs220_get_sysinfo_response_on");
}
@Test
public void testHandleCommandBrightnessOnOff() throws IOException {
assertInput("dimmer_set_switch_state_on");
setSocketReturnAssert("dimmer_set_switch_state_on");
assertTrue("Brightness channel as OnOffType type should be handled",
device.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON));
}
@Test
public void testHandleCommandBrightnessZero() throws IOException {
assertInput("dimmer_set_switch_state_off");
setSocketReturnAssert("dimmer_set_switch_state_response");
assertTrue("Brightness channel with percentage 0 should be handled",
device.handleCommand(CHANNEL_UID_BRIGHTNESS, PercentType.ZERO));
}
@Test
public void testHandleCommandBrightness() throws IOException {
assertInput("dimmer_set_brightness", "dimmer_set_switch_state_on");
setSocketReturnAssert("dimmer_set_brightness_response", "dimmer_set_switch_state_on");
assertTrue("Brightness channel should be handled",
device.handleCommand(CHANNEL_UID_BRIGHTNESS, new PercentType(17)));
}
@Test
public void testUpdateChannelSwitch() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson("hs220_get_sysinfo_response_off"));
assertSame("Dimmer device should be off", OnOffType.OFF,
((PercentType) device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState)).as(OnOffType.class));
}
@Test
public void testUpdateChannelBrightness() {
assertEquals("Dimmer brightness should be set", BRIGHTNESS_VALUE,
device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState));
}
@Test
public void testUpdateChannelOther() {
assertSame("Unknown channel should return UNDEF", UnDefType.UNDEF,
device.updateChannel(CHANNEL_UID_OTHER, deviceState));
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
/**
* Test class to test if text read from the device is correctly decoded to handle special characters.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class EncodingTest extends DeviceTestBase<SwitchDevice> {
public EncodingTest() throws IOException {
super(new SwitchDevice(), "encoding_test");
}
@Test
public void testCorrectDecodingOfText() throws IOException {
assertThat("Alias incorrectly decoded", deviceState.getSysinfo().getAlias(), is("MyßmärtPlug"));
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link EnergySwitchDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@RunWith(value = Parameterized.class)
@NonNullByDefault
public class EnergySwitchDeviceTest {
private static final List<Object[]> TESTS = Arrays
.asList(new Object[][] { { "plug_get_realtime_response", }, { "plug_get_realtime_response_v2", } });
private final EnergySwitchDevice device = new EnergySwitchDevice();
private final DeviceState deviceState;
public EnergySwitchDeviceTest(String name) throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(name));
}
@Parameters(name = "{0}")
public static List<Object[]> data() {
return TESTS;
}
@Test
public void testUpdateChannelEnergyCurrent() {
assertEquals("Energy current should have valid state value", new QuantityType<>(1 + " A"),
device.updateChannel(CHANNEL_UID_ENERGY_CURRENT, deviceState));
}
@Test
public void testUpdateChannelEnergyTotal() {
assertEquals("Energy total should have valid state value", new QuantityType<>(10 + " kWh"),
device.updateChannel(CHANNEL_UID_ENERGY_TOTAL, deviceState));
}
@Test
public void testUpdateChannelEnergyVoltage() {
State state = device.updateChannel(CHANNEL_UID_ENERGY_VOLTAGE, deviceState);
assertEquals("Energy voltage should have valid state value", 230, ((QuantityType<?>) state).intValue());
assertEquals("Channel patten to format voltage correctly", "230 V", state.format("%.0f %unit%"));
}
@Test
public void testUpdateChanneEnergyPower() {
assertEquals("Energy power should have valid state value", new QuantityType<>(20 + " W"),
device.updateChannel(CHANNEL_UID_ENERGY_POWER, deviceState));
}
@Test
public void testUpdateChannelOther() {
assertSame("Unknown channel should return UNDEF", UnDefType.UNDEF,
device.updateChannel(CHANNEL_UID_OTHER, deviceState));
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.HS300;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.binding.tplinksmarthome.internal.model.SetRelayState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.ChannelUID;
/**
* Test class for {@link PowerStripDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class PowerStripDeviceTest extends DeviceTestBase<PowerStripDevice> {
private static final ChannelUID CHANNEL_OUTLET_1 = ChannelUIDConstants.createChannel(HS300,
CHANNEL_OUTLET_GROUP_PREFIX + '1', CHANNEL_SWITCH);
private static final ChannelUID CHANNEL_OUTLET_2 = ChannelUIDConstants.createChannel(HS300,
CHANNEL_OUTLET_GROUP_PREFIX + '2', CHANNEL_SWITCH);
private static final ChannelUID CHANNEL_ENERGY_CURRENT_OUTLET_2 = ChannelUIDConstants.createChannel(HS300,
CHANNEL_OUTLET_GROUP_PREFIX + '2', CHANNEL_ENERGY_CURRENT);
private static final String[] REALTIME_INPUTS = IntStream.range(0, 6).mapToObj(i -> "hs300_get_realtime")
.collect(Collectors.toList()).toArray(new String[0]);
private static final String[] REALTIME_RESPONSES = IntStream.range(0, 6).mapToObj(i -> "plug_get_realtime_response")
.collect(Collectors.toList()).toArray(new String[0]);
public PowerStripDeviceTest() throws IOException {
super(new PowerStripDevice(HS300), "hs300_get_sysinfo_response");
}
@Override
@Before
public void setUp() throws IOException {
super.setUp();
final AtomicInteger inputCounter = new AtomicInteger(0);
final Function<String, String> inputWrapper = s -> s.replace("001", "00" + inputCounter.incrementAndGet());
assertInput(inputWrapper, Function.identity(), REALTIME_INPUTS);
setSocketReturnAssert(REALTIME_RESPONSES);
device.refreshedDeviceState(deviceState);
}
@Test
public void testHandleCommandSwitchChannel2() throws IOException {
Function<String, String> normalize = s -> ModelTestUtil.GSON
.toJson(ModelTestUtil.GSON.fromJson(s, SetRelayState.class));
assertInput(normalize, normalize, "hs300_set_relay_state");
setSocketReturnAssert("hs300_set_relay_state_response");
assertTrue("Outlet channel 2 should be handled", device.handleCommand(CHANNEL_OUTLET_2, OnOffType.ON));
}
@Test
public void testUpdateChannelOutlet1() {
assertSame("Outlet 1 should be on", OnOffType.ON, device.updateChannel(CHANNEL_OUTLET_1, deviceState));
}
@Test
public void testUpdateChannelOutlet2() {
assertSame("Outlet 2 should be off", OnOffType.OFF, device.updateChannel(CHANNEL_OUTLET_2, deviceState));
}
@Test
public void testUpdateChannelEnergyCurrent() {
assertEquals("Energy current should have valid state value", 1,
((QuantityType<?>) device.updateChannel(CHANNEL_ENERGY_CURRENT_OUTLET_2, deviceState)).intValue());
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link RangeExtenderDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class RangeExtenderDeviceTest extends DeviceTestBase<RangeExtenderDevice> {
public RangeExtenderDeviceTest() throws IOException {
super(new RangeExtenderDevice(), "rangeextender_get_sysinfo_response");
}
@Test
public void testHandleCommandSwitch() throws IOException {
assertFalse("Switch channel not yet supported so should not be handled",
device.handleCommand(CHANNEL_UID_SWITCH, OnOffType.ON));
}
@Test
public void testUpdateChannelSwitch() {
assertSame("Switch should be on", OnOffType.ON, device.updateChannel(CHANNEL_UID_SWITCH, deviceState));
}
@Test
public void testUpdateChannelLed() {
assertSame("Led should be on", OnOffType.ON, device.updateChannel(CHANNEL_UID_LED, deviceState));
}
@Test
public void testUpdateChannelOther() {
assertSame("Unknown channel should return UNDEF", UnDefType.UNDEF,
device.updateChannel(CHANNEL_UID_OTHER, deviceState));
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.Assert.*;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link SwitchDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class SwitchDeviceTest extends DeviceTestBase<SwitchDevice> {
public SwitchDeviceTest() throws IOException {
super(new SwitchDevice(), "plug_get_sysinfo_response");
}
@Test
public void testHandleCommandSwitch() throws IOException {
assertInput("plug_set_relay_state_on");
setSocketReturnAssert("plug_set_relay_state_on");
assertTrue("Switch channel should be handled", device.handleCommand(CHANNEL_UID_SWITCH, OnOffType.ON));
}
@Test
public void testHandleCommandLed() throws IOException {
assertInput("plug_set_led_on");
setSocketReturnAssert("plug_set_led_on");
assertTrue("Led channel should be handled", device.handleCommand(CHANNEL_UID_LED, OnOffType.ON));
}
@Test
public void testUpdateChannelSwitch() {
assertSame("Switch should be on", OnOffType.ON, device.updateChannel(CHANNEL_UID_SWITCH, deviceState));
}
@Test
public void testUpdateChannelLed() {
assertSame("Led should be on", OnOffType.ON, device.updateChannel(CHANNEL_UID_LED, deviceState));
}
@Test
public void testUpdateChannelOther() {
assertSame("Unknown channel should return UNDEF", UnDefType.UNDEF,
device.updateChannel(CHANNEL_UID_OTHER, deviceState));
}
}

View File

@@ -0,0 +1,148 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.handler;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_SWITCH;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants;
import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.Connection;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeConfiguration;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeDiscoveryService;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType;
import org.openhab.binding.tplinksmarthome.internal.device.SmartHomeDevice;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
/**
* Tests cases for {@link SmartHomeHandler} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class SmartHomeHandlerTest {
private @NonNullByDefault({}) SmartHomeHandler handler;
@Mock
private @NonNullByDefault({}) Connection connection;
@Mock
private @NonNullByDefault({}) ThingHandlerCallback callback;
@Mock
private @NonNullByDefault({}) Thing thing;
@Mock
private @NonNullByDefault({}) SmartHomeDevice smartHomeDevice;
@Mock
private @NonNullByDefault({}) TPLinkSmartHomeDiscoveryService discoveryService;
private final Configuration configuration = new Configuration();
@Before
public void setUp() throws IOException {
initMocks(this);
configuration.put(CONFIG_IP, "localhost");
configuration.put(CONFIG_REFRESH, 1);
when(thing.getConfiguration()).thenReturn(configuration);
when(smartHomeDevice.getUpdateCommand()).thenReturn(Commands.getSysinfo());
when(connection.sendCommand(Commands.getSysinfo()))
.thenReturn(ModelTestUtil.readJson("plug_get_sysinfo_response"));
handler = new SmartHomeHandler(thing, smartHomeDevice, TPLinkSmartHomeThingType.HS100, discoveryService) {
@Override
Connection createConnection(TPLinkSmartHomeConfiguration config) {
return connection;
}
};
when(smartHomeDevice.handleCommand(eq(CHANNEL_UID_SWITCH), any())).thenReturn(true);
when(callback.isChannelLinked(any())).thenReturn(true);
handler.setCallback(callback);
}
@After
public void after() {
handler.dispose();
}
@Test
public void testInitializeShouldCallTheCallback() throws InterruptedException {
handler.initialize();
ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
verify(callback).statusUpdated(eq(thing), statusInfoCaptor.capture());
ThingStatusInfo thingStatusInfo = statusInfoCaptor.getValue();
assertEquals("Device should be unknown", ThingStatus.UNKNOWN, thingStatusInfo.getStatus());
}
@Test
public void testHandleCommandRefreshType() {
handler.initialize();
assertHandleCommandRefreshType(-53);
}
@Test
public void testHandleCommandRefreshTypeRangeExtender() throws IOException {
when(connection.sendCommand(Commands.getSysinfo()))
.thenReturn(ModelTestUtil.readJson("rangeextender_get_sysinfo_response"));
handler.initialize();
assertHandleCommandRefreshType(-70);
}
private void assertHandleCommandRefreshType(int expectedRssi) {
handler.initialize();
ChannelUID channelUID = ChannelUIDConstants.CHANNEL_UID_RSSI;
handler.handleCommand(channelUID, RefreshType.REFRESH);
ArgumentCaptor<State> stateCaptor = ArgumentCaptor.forClass(State.class);
verify(callback).stateUpdated(eq(channelUID), stateCaptor.capture());
assertEquals("State of RSSI channel should be set", new QuantityType<>(expectedRssi + " dBm"),
stateCaptor.getValue());
}
@Test
public void testHandleCommandOther() throws InterruptedException {
handler.initialize();
ChannelUID channelUID = ChannelUIDConstants.CHANNEL_UID_SWITCH;
Mockito.doReturn(OnOffType.ON).when(smartHomeDevice).updateChannel(eq(channelUID), any());
handler.handleCommand(channelUID, RefreshType.REFRESH);
ArgumentCaptor<State> stateCaptor = ArgumentCaptor.forClass(State.class);
verify(callback).stateUpdated(eq(channelUID), stateCaptor.capture());
assertSame("State of channel switch should be set", OnOffType.ON, stateCaptor.getValue());
}
@Test
public void testRefreshChannels() {
handler.initialize();
handler.refreshChannels();
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tplinksmarthome.internal.model;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.Gson;
/**
* Util class for reading test resources.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public final class ModelTestUtil {
public static final Gson GSON = GsonUtil.createGson();
private ModelTestUtil() {
// Util class
}
/**
* Util method to read a json file into it's data class.
*
* @param <T> Type of the class the json data represents.
* @param gson gson class
* @param filename filename of the json file to read. The file is read relative to the directory of this class
* @param clazz Data class expected to be read from the json file
* @return instance of clazz with read data from json file
* @throws IOException when file could not be read.
*/
public static <T> T jsonFromFile(String filename, Class<T> clazz) throws IOException {
return GSON.fromJson(readJson(filename), clazz);
}
/**
* Util method to read a json file. It normalizes the string by removing returns, tabs and spaces. It's not very
* smart as it removes spaces inside json values as well. But this method is mainly intended be able to compare 2
* json strings.
*
* @param filename filename of the json file to read. The file is read relative to the directory of this class
* @return read json string
* @throws IOException when file could not be read.
*/
public static String readJson(String filename) throws IOException {
return IOUtils
.toString(ModelTestUtil.class.getResourceAsStream(filename + ".json"), StandardCharsets.UTF_8.name())
.replaceAll("[\n\r\t ]", "");
}
}

View File

@@ -0,0 +1 @@
{"smartlife.iot.common.emeter":{"get_realtime":{}}}

View File

@@ -0,0 +1,75 @@
{
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Downstairs Light",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Dimmable Light",
"dev_state": "normal",
"deviceId": "80120B3D03E0B639CDF33E3CB1466490187FEF32",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 309908,
"hwId": "111E35908497A05512E259BB76801E10",
"hw_ver": "1.0",
"is_color": 0,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 0,
"light_state": {
"dft_on_state": {
"brightness": 92,
"color_temp": 2700,
"hue": 7,
"mode": "normal",
"saturation": 44
},
"on_off": 0
},
"mic_mac": "50C7BF7BE306",
"mic_type": "IOT.SMARTBULB",
"model": "LB110(EU)",
"oemId": "A68E15472071CB761E5CCFB388A1D8AE",
"preferred_state": [
{
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 58,
"color_temp": 2700,
"hue": 0,
"index": 1,
"saturation": 0
},
{
"brightness": 25,
"color_temp": 2700,
"hue": 0,
"index": 2,
"saturation": 0
},
{
"brightness": 1,
"color_temp": 2700,
"hue": 0,
"index": 3,
"saturation": 0
}
],
"rssi": -61,
"sw_ver": "1.5.5 Build 170623 Rel.090105"
}
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"power_mw": 10800
}
}
}

View File

@@ -0,0 +1,72 @@
{
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "Living Room Side Table",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Color Changing",
"dev_state": "normal",
"deviceId": "DEVID_HERE",
"disco_ver": "1.0",
"heapsize": 347000,
"hwId": "HWID_HERE",
"hw_ver": "1.0",
"is_color": 1,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 1,
"light_state": {
"brightness": 92,
"color_temp": 2630,
"hue": 7,
"mode": "normal",
"on_off": 1,
"saturation": 44
},
"mic_mac": "MAC_HERE",
"mic_type": "IOT.SMARTBULB",
"model": "LB130(US)",
"oemId": "EOMiID_HERE",
"preferred_state": [
{
"brightness": 50,
"color_temp": 2700,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 100,
"color_temp": 0,
"hue": 0,
"index": 1,
"saturation": 75
},
{
"brightness": 100,
"color_temp": 0,
"hue": 120,
"index": 2,
"saturation": 75
},
{
"brightness": 100,
"color_temp": 0,
"hue": 240,
"index": 3,
"saturation": 75
}
],
"rssi": -55,
"sw_ver": "1.1.2 Build 160927 Rel.111100"
}
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"power_mw": 10800
}
}
}

View File

@@ -0,0 +1,11 @@
{
"smartlife.iot.smartbulb.lightingservice": {
"transition_light_state": {
"brightness": 33,
"on_off": 1,
"ignore_default": 1,
"mode": "normal",
"transition_period": 10
}
}
}

View File

@@ -0,0 +1,14 @@
{
"smartlife.iot.smartbulb.lightingservice": {
"transition_light_state": {
"color_temp": 0,
"hue": 55,
"saturation": 44,
"brightness": 33,
"on_off": 1,
"ignore_default": 1,
"mode": "normal",
"transition_period": 10
}
}
}

View File

@@ -0,0 +1,13 @@
{
"smartlife.iot.smartbulb.lightingservice": {
"transition_light_state": {
"color_temp": 5100,
"hue": 0,
"saturation": 0,
"on_off": 1,
"ignore_default": 1,
"mode": "normal",
"transition_period": 10
}
}
}

View File

@@ -0,0 +1,10 @@
{
"smartlife.iot.smartbulb.lightingservice": {
"transition_light_state": {
"on_off": 1,
"ignore_default": 1,
"mode": "normal",
"transition_period": 10
}
}
}

View File

@@ -0,0 +1,13 @@
{
"smartlife.iot.smartbulb.lightingservice": {
"transition_light_state": {
"on_off": 1,
"mode": "normal",
"hue": 120,
"saturation": 65,
"color_temp": 0,
"brightness": 10,
"err_code": 0
}
}
}

View File

@@ -0,0 +1 @@
{"smartlife.iot.dimmer":{"set_brightness":{"brightness":17}}}

View File

@@ -0,0 +1 @@
{"smartlife.iot.dimmer":{"set_brightness":{"err_code":0}}}

View File

@@ -0,0 +1 @@
{"smartlife.iot.dimmer":{"set_switch_state":{"state":0}}}

View File

@@ -0,0 +1 @@
{"smartlife.iot.dimmer":{"set_switch_state":{"state":1}}}

View File

@@ -0,0 +1 @@
{"smartlife.iot.dimmer":{"set_switch_state":{"err_code":0}}}

Some files were not shown because too many files have changed in this diff Show More