[tplinksmarthome] Added KL400, KL430 lightstrip support (#12000)

Closes #8709

Additional:
- Added action to send and receive json commands to a tplink device. This can be used for test purposes or to run commands not available through channels.

Fixes:
- Power channel of a bulb is defined as QuantityType in xml, therefor it should create the state using QuantityType
- Retry getting values 5 times before setting the device offline. Reduced socket time out to 2 seconds as it normally should react quickly and if it times out it tries again.

Also-by: Dustin Masters <ceo@dustinsoftware.com>
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
This commit is contained in:
Hilbrand Bouwkamp 2022-02-10 19:58:24 +01:00 committed by GitHub
parent a0236bf993
commit 888cf00245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 944 additions and 131 deletions

View File

@ -242,6 +242,24 @@ Switching, Brightness and Color is done using the `color` channel.
Switching, Brightness and Color is done using the `color` channel.
### KL400 Kasa Smart LED Light Strip
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* Wi-Fi signal strength (RSSI)
### KL430 Kasa Smart LED Light Strip, 16 Color Zones
* Power On/Off
* Fine-tune colors
* Adjust light appearance from soft white (2500k) to daylight (9000k)
* Adjust the brightness
* 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
@ -363,9 +381,9 @@ All devices support some of the following channels:
|---------------------|--------------------------|------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| switch | Switch | Power the device on or off. | EP10, EP40, HS100, HS103, HS105, HS107, HS110, HS200, HS210, HS300, KP100, KP105, KP115, KP200, KP303, KP400, KS230, RE270K, RE370K |
| brightness | Dimmer | Set the brightness of device or dimmer. | ES20M, HS220, KB100, KL50, KL60, KL110, KL120, KP405, LB100, LB110, LB120, LB200 |
| colorTemperature | Dimmer | Set the color temperature in percentage. | KB130, KL120, KL125, KL130, KL135, LB120, LB130, LB230 |
| colorTemperatureAbs | Number | Set the color temperature in Kelvin. | KB130, KL120, KL125, KL130, KL135, LB120, LB130, LB230 |
| color | Color | Set the color of the light. | KB130, KL125, KL130, KL135, LB130, LB230 |
| colorTemperature | Dimmer | Set the color temperature in percentage. | KB130, KL120, KL125, KL130, KL135, KL400, KL430, LB120, LB130, LB230 |
| colorTemperatureAbs | Number | Set the color temperature in Kelvin. | KB130, KL120, KL125, KL130, KL135, KL400, KL430, LB120, LB130, LB230 |
| color | Color | Set the color of the light. | KB130, KL125, KL130, KL135, KL400, KL430, LB130, LB230 |
| power | Number:Power | Actual energy usage in Watt. | HS110, HS300, KLxxx, KP115, KP125, LBxxx, |
| eneryUsage | Number:Energy | Energy Usage in kWh. | HS110, HS300, KP115, KP125 |
| current | Number:ElectricCurrent | Actual current usage in Ampere. | HS110, HS300, KP115, KP125 |

View File

@ -21,6 +21,7 @@ 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.SetLightState;
import org.openhab.binding.tplinksmarthome.internal.model.SetRelayState;
import org.openhab.binding.tplinksmarthome.internal.model.SetSwitchState;
import org.openhab.binding.tplinksmarthome.internal.model.Sysinfo;
@ -79,7 +80,7 @@ public class Commands {
* @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) {
public static String getRealtimeWithContext(final String id) {
return String.format(CONTEXT, id) + REALTIME + "}";
}
@ -90,8 +91,8 @@ public class Commands {
* @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);
public Realtime getRealtimeResponse(final String realtimeResponse) {
final GetRealtime getRealtime = gson.fromJson(realtimeResponse, GetRealtime.class);
return getRealtime == null ? new Realtime() : getRealtime.getRealtime();
}
@ -111,8 +112,8 @@ public class Commands {
* @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);
public Sysinfo getSysinfoReponse(final String getSysinfoReponse) {
final GetSysinfo getSysinfo = gson.fromJson(getSysinfoReponse, GetSysinfo.class);
return getSysinfo == null ? new Sysinfo() : getSysinfo.getSysinfo();
}
@ -123,8 +124,8 @@ public class Commands {
* @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();
public String setRelayState(final OnOffType onOff, @Nullable final String childId) {
final SetRelayState relayState = new SetRelayState();
relayState.setRelayState(onOff);
if (childId != null) {
relayState.setChildId(childId);
@ -138,7 +139,7 @@ public class Commands {
* @param relayStateResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable SetRelayState setRelayStateResponse(String relayStateResponse) {
public @Nullable SetRelayState setRelayStateResponse(final String relayStateResponse) {
return gsonWithExpose.fromJson(relayStateResponse, SetRelayState.class);
}
@ -148,8 +149,8 @@ public class Commands {
* @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();
public String setSwitchState(final OnOffType onOff) {
final SetSwitchState switchState = new SetSwitchState();
switchState.setSwitchState(onOff);
return gsonWithExpose.toJson(switchState);
}
@ -160,7 +161,7 @@ public class Commands {
* @param switchStateResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable SetSwitchState setSwitchStateResponse(String switchStateResponse) {
public @Nullable SetSwitchState setSwitchStateResponse(final String switchStateResponse) {
return gsonWithExpose.fromJson(switchStateResponse, SetSwitchState.class);
}
@ -170,8 +171,8 @@ public class Commands {
* @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();
public String setDimmerBrightness(final int brightness) {
final SetBrightness setBrightness = new SetBrightness();
setBrightness.setBrightness(brightness);
return gsonWithExpose.toJson(setBrightness);
}
@ -182,26 +183,10 @@ public class Commands {
* @param dimmerBrightnessResponse the json string
* @return The data object containing the state data from the json string
*/
public @Nullable HasErrorResponse setDimmerBrightnessResponse(String dimmerBrightnessResponse) {
public @Nullable HasErrorResponse setDimmerBrightnessResponse(final 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.
*
@ -209,8 +194,8 @@ public class Commands {
* @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();
public String setLedOn(final OnOffType onOff, @Nullable final String childId) {
final SetLedOff sLOff = new SetLedOff();
sLOff.setLed(onOff);
if (childId != null) {
sLOff.setChildId(childId);
@ -224,10 +209,21 @@ public class Commands {
* @param setLedOnResponse the json string
* @return The data object containing the data from the json string
*/
public @Nullable SetLedOff setLedOnResponse(String setLedOnResponse) {
public @Nullable SetLedOff setLedOnResponse(final String setLedOnResponse) {
return gsonWithExpose.fromJson(setLedOnResponse, SetLedOff.class);
}
/**
* Returns the json for the transition_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 setTransitionLightState(final OnOffType onOff, final int transitionPeriod) {
return setTransitionLightState(new LightOnOff(), onOff, transitionPeriod);
}
/**
* Returns the json for the set_light_State command to set the brightness.
*
@ -235,14 +231,10 @@ public class Commands {
* @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);
public String setTransitionLightStateBrightness(final int brightness, final int transitionPeriod) {
final LightStateBrightness lightState = new LightStateBrightness();
lightState.setBrightness(brightness);
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
return setTransitionLightState(lightState, OnOffType.from(brightness != 0), transitionPeriod);
}
/**
@ -252,17 +244,13 @@ public class Commands {
* @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);
public String setTransitionLightStateColor(final HSBType hsb, final int transitionPeriod) {
final LightStateColor lightState = new LightStateColor();
final int brightness = hsb.getBrightness().intValue();
lightState.setBrightness(brightness);
lightState.setHue(hsb.getHue().intValue());
lightState.setSaturation(hsb.getSaturation().intValue());
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return gson.toJson(transitionLightState);
return setTransitionLightState(lightState, OnOffType.from(brightness != 0), transitionPeriod);
}
/**
@ -272,13 +260,18 @@ public class Commands {
* @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);
public String setColorTemperature(final int colorTemperature, final int transitionPeriod) {
final LightStateColorTemperature lightState = new LightStateColorTemperature();
lightState.setColorTemperature(colorTemperature);
lightState.setTransitionPeriod(transitionPeriod);
transitionLightState.setLightState(lightState);
return setTransitionLightState(lightState, OnOffType.ON, transitionPeriod);
}
private String setTransitionLightState(final LightOnOff lightOnOff, final OnOffType onOff,
final int transitionPeriod) {
final TransitionLightState transitionLightState = new TransitionLightState();
transitionLightState.setLightState(lightOnOff);
lightOnOff.setOnOff(onOff);
lightOnOff.setTransitionPeriod(transitionPeriod);
return gson.toJson(transitionLightState);
}
@ -288,7 +281,82 @@ public class Commands {
* @param response the json string
* @return The data object containing the state data from the json string
*/
public @Nullable TransitionLightStateResponse setTransitionLightStateResponse(String response) {
public @Nullable TransitionLightStateResponse setTransitionLightStateResponse(final String response) {
return gson.fromJson(response, TransitionLightStateResponse.class);
}
// ---------------------------------------------------------------
/**
* Returns the json for the set_light_state command to switch a light strip on or off.
*
* @param onOff the switch state to set
* @param transition the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setLightStripState(final OnOffType onOff, final int transition) {
return setLightStripState(new SetLightState.LightOnOff(), onOff, transition);
}
/**
* Returns the json for the set_light_State command to set the brightness.
*
* @param brightness the brightness value
* @param transition the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setLightStripBrightness(final int brightness, final int transition) {
final SetLightState.Brightness lightState = new SetLightState.Brightness();
lightState.setBrightness(brightness);
return setLightStripState(lightState, OnOffType.from(brightness != 0), transition);
}
/**
* Returns the json for the set_light_State command to set the color.
*
* @param hsb the color to set
* @param transition the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setLightStripColor(final HSBType hsb, final int transition) {
final SetLightState.Color lightState = new SetLightState.Color();
final int brightness = hsb.getBrightness().intValue();
lightState.setHue(hsb.getHue().intValue());
lightState.setSaturation(hsb.getSaturation().intValue());
lightState.setBrightness(brightness);
return setLightStripState(lightState, OnOffType.from(brightness != 0), transition);
}
/**
* Returns the json for the set_light_State command to set the color temperature.
*
* @param colorTemperature the color temperature to set
* @param transition the transition period for the action to take place
* @return The json string of the command to send to the device
*/
public String setLightStripColorTemperature(final int colorTemperature, final int transition) {
final SetLightState.ColorTemperature lightState = new SetLightState.ColorTemperature();
lightState.setColorTemp(colorTemperature);
return setLightStripState(lightState, OnOffType.ON, transition);
}
private String setLightStripState(final SetLightState.LightOnOff lightOnOff, final OnOffType onOff,
final int transition) {
final SetLightState setLightState = new SetLightState();
setLightState.setContext(new SetLightState.Context());
setLightState.setLightState(lightOnOff);
lightOnOff.setOnOff(onOff);
lightOnOff.setTransition(transition);
return gsonWithExpose.toJson(setLightState);
}
/**
* 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 SetLightState setLightStripStateResponse(final String response) {
return gsonWithExpose.fromJson(response, SetLightState.class);
}
}

View File

@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
public class Connection {
public static final int TP_LINK_SMART_HOME_PORT = 9999;
private static final int SOCKET_TIMEOUT_MILLISECONDS = 3_000;
private static final int SOCKET_TIMEOUT_MILLISECONDS = 2_000;
private final Logger logger = LoggerFactory.getLogger(Connection.class);

View File

@ -19,6 +19,7 @@ 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.LightStripDevice;
import org.openhab.binding.tplinksmarthome.internal.device.PowerStripDevice;
import org.openhab.binding.tplinksmarthome.internal.device.RangeExtenderDevice;
import org.openhab.binding.tplinksmarthome.internal.device.SmartHomeDevice;
@ -67,6 +68,9 @@ public class TPLinkSmartHomeHandlerFactory extends BaseThingHandlerFactory {
case DIMMER:
device = new DimmerDevice();
break;
case LIGHT_STRIP:
device = new LightStripDevice(type);
break;
case PLUG:
device = new SwitchDevice();
break;

View File

@ -49,6 +49,10 @@ public enum TPLinkSmartHomeThingType {
KL130("kl130", DeviceType.BULB, ColorScales.K_2500_9000),
KL135("kl135", DeviceType.BULB, ColorScales.K_2500_6500),
// Light String thing Type UIDs.
KL400("kl400", DeviceType.LIGHT_STRIP, ColorScales.K_2500_9000),
KL430("kl430", DeviceType.LIGHT_STRIP, ColorScales.K_2500_9000),
// Plug Thing Type UIDs
EP10("ep10", DeviceType.PLUG),
HS100("hs100", DeviceType.PLUG),
@ -175,6 +179,10 @@ public enum TPLinkSmartHomeThingType {
* Dimmer device.
*/
DIMMER,
/**
* Light Strip device.
*/
LIGHT_STRIP,
/**
* Plug device.
*/

View File

@ -28,11 +28,12 @@ import org.openhab.binding.tplinksmarthome.internal.Commands;
import org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType;
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.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@ -69,9 +70,9 @@ public class BulbDevice extends SmartHomeDevice {
final int transitionPeriod = configuration.transitionPeriod;
final HasErrorResponse response;
if (command instanceof OnOffType) {
if (command instanceof OnOffType && CHANNELS_BULB_SWITCH.contains(channelId)) {
response = handleOnOffType(channelId, (OnOffType) command, transitionPeriod);
} else if (command instanceof HSBType) {
} else if (command instanceof HSBType && CHANNEL_COLOR.equals(channelId)) {
response = handleHSBType(channelId, (HSBType) command, transitionPeriod);
} else if (command instanceof DecimalType) {
response = handleDecimalType(channelId, (DecimalType) command, transitionPeriod);
@ -82,42 +83,42 @@ public class BulbDevice extends SmartHomeDevice {
return response != null;
}
private @Nullable HasErrorResponse handleOnOffType(final String channelID, final OnOffType onOff,
protected @Nullable HasErrorResponse handleOnOffType(final String channelID, final OnOffType onOff,
final int transitionPeriod) throws IOException {
if (CHANNELS_BULB_SWITCH.contains(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setLightState(onOff, transitionPeriod)));
}
return null;
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setTransitionLightState(onOff, transitionPeriod)));
}
private @Nullable HasErrorResponse handleDecimalType(final String channelID, final DecimalType command,
final int transitionPeriod) throws IOException {
final int intValue = command.intValue();
if (CHANNEL_COLOR.equals(channelID) || CHANNEL_BRIGHTNESS.equals(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setBrightness(command.intValue(), transitionPeriod)));
return handleBrightness(intValue, transitionPeriod);
} else if (CHANNEL_COLOR_TEMPERATURE.equals(channelID)) {
return handleColorTemperature(convertPercentageToKelvin(command.intValue()), transitionPeriod);
return handleColorTemperature(convertPercentageToKelvin(intValue), transitionPeriod);
} else if (CHANNEL_COLOR_TEMPERATURE_ABS.equals(channelID)) {
return handleColorTemperature(guardColorTemperature(command.intValue()), transitionPeriod);
return handleColorTemperature(guardColorTemperature(intValue), transitionPeriod);
}
return null;
}
private @Nullable TransitionLightStateResponse handleColorTemperature(final int colorTemperature,
final int transitionPeriod) throws IOException {
protected @Nullable HasErrorResponse handleBrightness(final int brightness, final int transitionPeriod)
throws IOException {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setTransitionLightStateBrightness(brightness, transitionPeriod)));
}
protected @Nullable HasErrorResponse handleColorTemperature(final int colorTemperature, final int transitionPeriod)
throws IOException {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setColorTemperature(colorTemperature, transitionPeriod)));
}
@Nullable
private HasErrorResponse handleHSBType(final String channelID, final HSBType command, final int transitionPeriod)
throws IOException {
if (CHANNEL_COLOR.equals(channelID)) {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setColor(command, transitionPeriod)));
}
return null;
protected @Nullable HasErrorResponse handleHSBType(final String channelID, final HSBType command,
final int transitionPeriod) throws IOException {
return commands.setTransitionLightStateResponse(
connection.sendCommand(commands.setTransitionLightStateColor(command, transitionPeriod)));
}
@Override
@ -142,7 +143,7 @@ public class BulbDevice extends SmartHomeDevice {
state = lightState.getOnOff();
break;
case CHANNEL_ENERGY_POWER:
state = new DecimalType(deviceState.getRealtime().getPower());
state = new QuantityType<>(deviceState.getRealtime().getPower(), Units.WATT);
break;
default:
state = UnDefType.UNDEF;

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2022 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.TPLinkSmartHomeThingType;
import org.openhab.binding.tplinksmarthome.internal.model.HasErrorResponse;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
/**
* TP-Link Smart Home Light Strip.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class LightStripDevice extends BulbDevice {
public LightStripDevice(final TPLinkSmartHomeThingType type) {
super(type);
}
@Override
protected @Nullable HasErrorResponse handleOnOffType(final String channelID, final OnOffType onOff,
final int transitionPeriod) throws IOException {
return commands.setLightStripStateResponse(
connection.sendCommand(commands.setLightStripState(onOff, transitionPeriod)));
}
@Override
protected @Nullable HasErrorResponse handleBrightness(final int brightness, final int transitionPeriod)
throws IOException {
return commands.setLightStripStateResponse(
connection.sendCommand(commands.setLightStripBrightness(brightness, transitionPeriod)));
}
@Override
protected @Nullable HasErrorResponse handleColorTemperature(final int colorTemperature, final int transitionPeriod)
throws IOException {
return commands.setLightStripStateResponse(
connection.sendCommand(commands.setLightStripColorTemperature(colorTemperature, transitionPeriod)));
}
@Override
protected @Nullable HasErrorResponse handleHSBType(final String channelID, final HSBType command,
final int transitionPeriod) throws IOException {
return commands.setLightStripStateResponse(
connection.sendCommand(commands.setLightStripColor(command, transitionPeriod)));
}
}

View File

@ -12,10 +12,16 @@
*/
package org.openhab.binding.tplinksmarthome.internal.handler;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.*;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.CHANNEL_RSSI;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.CONFIG_DEVICE_ID;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.CONFIG_IP;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.FORCED_REFRESH_BOUNDERY_SECONDS;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeBindingConstants.FORCED_REFRESH_BOUNDERY_SWITCHED_SECONDS;
import java.io.IOException;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -38,6 +44,7 @@ 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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
@ -55,6 +62,7 @@ import org.slf4j.LoggerFactory;
public class SmartHomeHandler extends BaseThingHandler {
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
private static final int CONNECTION_IO_RETRIES = 5;
private final Logger logger = LoggerFactory.getLogger(SmartHomeHandler.class);
@ -79,8 +87,8 @@ public class SmartHomeHandler extends BaseThingHandler {
* @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) {
public SmartHomeHandler(final Thing thing, final SmartHomeDevice smartHomeDevice,
final TPLinkSmartHomeThingType type, final TPLinkIpAddressService ipAddressService) {
super(thing);
this.smartHomeDevice = smartHomeDevice;
this.ipAddressService = ipAddressService;
@ -90,7 +98,16 @@ public class SmartHomeHandler extends BaseThingHandler {
}
@Override
public void handleCommand(ChannelUID channelUid, Command command) {
public Collection<Class<? extends ThingHandlerService>> getServices() {
return List.of(TPLinkSmartHomeActions.class);
}
Connection getConnection() {
return connection;
}
@Override
public void handleCommand(final ChannelUID channelUid, final Command command) {
try {
if (command instanceof RefreshType) {
updateChannelState(channelUid, fastCache.getValue());
@ -100,7 +117,7 @@ public class SmartHomeHandler extends BaseThingHandler {
} else {
logger.debug("Command {} is not supported for channel: {}", command, channelUid.getId());
}
} catch (IOException e) {
} catch (final IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@ -143,7 +160,7 @@ public class SmartHomeHandler extends BaseThingHandler {
* @param config configuration to be used by the connection
* @return new Connection object
*/
Connection createConnection(TPLinkSmartHomeConfiguration config) {
Connection createConnection(final TPLinkSmartHomeConfiguration config) {
return new Connection(config.ipAddress);
}
@ -158,22 +175,34 @@ public class SmartHomeHandler extends BaseThingHandler {
}
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);
int retry = 1;
while (true) {
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 (final IOException e) {
// If there is a connection problem retry before throwing an exception
if (retry < CONNECTION_IO_RETRIES) {
logger.trace("Communication error, retry {}", retry, e);
retry++;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
return null;
}
} catch (final 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;
}
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;
}
}
@ -187,10 +216,10 @@ public class SmartHomeHandler extends BaseThingHandler {
// 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);
final String lastKnownIpAddress = ipAddressService.getLastKnownIpAddress(configuration.deviceId);
if (lastKnownIpAddress != null && !lastKnownIpAddress.equals(configuration.ipAddress)) {
Configuration editConfig = editConfiguration();
final Configuration editConfig = editConfiguration();
editConfig.put(CONFIG_IP, lastKnownIpAddress);
updateConfiguration(editConfig);
configuration.ipAddress = lastKnownIpAddress;
@ -206,9 +235,9 @@ public class SmartHomeHandler extends BaseThingHandler {
* @throws IllegalArgumentException if the configured device id doesn't match with the id reported by the device
* itself.
*/
private void updateDeviceId(String actualDeviceId) {
private void updateDeviceId(final String actualDeviceId) {
if (StringUtil.isBlank(configuration.deviceId)) {
Configuration editConfig = editConfiguration();
final Configuration editConfig = editConfiguration();
editConfig.put(CONFIG_DEVICE_ID, actualDeviceId);
updateConfiguration(editConfig);
configuration.deviceId = actualDeviceId;
@ -222,7 +251,7 @@ public class SmartHomeHandler extends BaseThingHandler {
/**
* Starts the background refresh thread.
*/
private void startAutomaticRefresh(TPLinkSmartHomeConfiguration config) {
private void startAutomaticRefresh(final TPLinkSmartHomeConfiguration config) {
if (refreshJob == null || refreshJob.isCancelled()) {
refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, config.refresh, config.refresh,
TimeUnit.SECONDS);
@ -241,11 +270,11 @@ public class SmartHomeHandler extends BaseThingHandler {
* @param deviceState the state object containing the value to set of the channel
*
*/
private void updateChannelState(ChannelUID channelUID, @Nullable DeviceState deviceState) {
private void updateChannelState(final ChannelUID channelUID, @Nullable final DeviceState deviceState) {
if (!isLinked(channelUID)) {
return;
}
String channelId = channelUID.isInGroup() ? channelUID.getIdWithoutGroup() : channelUID.getId();
final String channelId = channelUID.isInGroup() ? channelUID.getIdWithoutGroup() : channelUID.getId();
final State state;
if (deviceState == null) {

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2022 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 java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TP-Link Smart Home Rule Actions.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@ThingActionsScope(name = "tplinksmarthome")
@NonNullByDefault
public class TPLinkSmartHomeActions implements ThingActions, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(TPLinkSmartHomeActions.class);
private @Nullable SmartHomeHandler handler;
@RuleAction(label = "@text/actions.tplinksmarthome.send.label", description = "@text/actions.tplinksmarthome.send.description")
@ActionOutput(name = "response", label = "@text/actions.tplinksmarthome.send.response.label", description = "@text/actions.tplinksmarthome.send.response.description", type = "java.lang.String")
public String send(
@ActionInput(name = "command", label = "@text/actions.tplinksmarthome.send.command.label", description = "@text/actions.tplinksmarthome.send.command.description", type = "java.lang.String", required = true) final String command)
throws IOException {
if (handler instanceof SmartHomeHandler) {
return handler.getConnection().sendCommand(command);
} else {
logger.warn("Could not send command to tplink device because handler not set.");
return "";
}
}
public static String send(final ThingActions actions, final String command) throws IOException {
return ((TPLinkSmartHomeActions) actions).send(command);
}
@Override
public void setThingHandler(final ThingHandler handler) {
if (handler instanceof SmartHomeHandler) {
this.handler = (SmartHomeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
}

View File

@ -0,0 +1,175 @@
/**
* Copyright (c) 2010-2022 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.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
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 Light Strip state and retrieving the result.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
public class SetLightState implements HasErrorResponse {
private static final int GROUPS_INDEX_HUE = 2;
private static final int GROUPS_INDEX_SATURATION = 3;
private static final int GROUPS_INDEX_BRIGHTNESS = 4;
private static final int GROUPS_INDEX_COLOR_TEMPERATURE = 5;
public static class ColorTemperature extends LightOnOff {
@Expose(deserialize = false)
private int colorTemp;
@Expose(deserialize = false)
private int hue = 0;
@Expose(deserialize = false)
private int saturation = 0;
public void setColorTemp(final int colorTemperature) {
this.colorTemp = colorTemperature;
}
}
public static class Color extends Brightness {
@Expose(deserialize = false)
private int colorTemp;
@Expose(deserialize = false)
private int hue;
@Expose(deserialize = false)
private int saturation;
public void setHue(final int hue) {
this.hue = hue;
}
public void setSaturation(final int saturation) {
this.saturation = saturation;
}
}
public static class Brightness extends LightOnOff {
@Expose(deserialize = false)
private int brightness;
public void setBrightness(final int brightness) {
this.brightness = brightness;
}
}
public static class LightOnOff extends ErrorResponse {
@Expose
private int onOff;
@Expose(serialize = false)
private String mode;
@Expose(deserialize = false)
private int transition;
/**
* groups contain status: [[0,31,0,0,73,5275]]
* [?,?,hue,saturation,brightness,color_temp]
*/
@Expose(serialize = false)
protected int[][] groups;
public OnOffType getOnOff() {
return OnOffType.from(onOff == 1);
}
public void setOnOff(final OnOffType onOff) {
this.onOff = onOff == OnOffType.ON ? 1 : 0;
}
public void setTransition(final int transition) {
this.transition = transition;
}
public int getHue() {
return groups[0][GROUPS_INDEX_HUE];
}
public int getSaturation() {
return groups[0][GROUPS_INDEX_SATURATION];
}
public int getBrightness() {
return groups[0][GROUPS_INDEX_BRIGHTNESS];
}
public int getColorTemperature() {
return groups[0][GROUPS_INDEX_COLOR_TEMPERATURE];
}
public int[][] getGroups() {
return groups;
}
@Override
public String toString() {
return "onOff:" + onOff + ", mode:" + mode + ", transition:" + transition + ", groups:"
+ Arrays.toString(groups);
}
}
public static class Context {
@Expose
private String source = "12345668-1234-1234-1234-123456789012";
public String getSource() {
return source;
}
public void setSource(final String source) {
this.source = source;
}
}
public static class LightingStrip {
@Expose
private LightOnOff setLightState;
@Override
public String toString() {
return "setLightState:{" + setLightState + "}";
}
}
@NonNullByDefault
@SerializedName("smartlife.iot.lightStrip")
@Expose
private final LightingStrip strip = new LightingStrip();
@Expose(deserialize = false)
private Context context;
public void setLightState(final LightOnOff lightState) {
strip.setLightState = lightState;
}
public void setContext(final Context context) {
this.context = context;
}
@Override
public ErrorResponse getErrorResponse() {
return strip.setLightState;
}
@Override
public String toString() {
return "SetLightState {strip:{" + strip + "}";
}
}

View File

@ -53,6 +53,10 @@ thing-type.tplinksmarthome.kl130.label = KL130
thing-type.tplinksmarthome.kl130.description = TP-Link KL130 Smart Wi-Fi LED Bulb with Color Changing Hue
thing-type.tplinksmarthome.kl135.label = KL135
thing-type.tplinksmarthome.kl135.description = TP-Link KL135 Kasa Smart Wi-Fi Bulb Multicolor
thing-type.tplinksmarthome.kl400.label = KL400
thing-type.tplinksmarthome.kl400.description = TP-Link KL400 Kasa Smart Light Strip, Multicolour
thing-type.tplinksmarthome.kl430.label = KL430
thing-type.tplinksmarthome.kl430.description = TP-Link KL430 Kasa Smart Light Strip, Multicolour
thing-type.tplinksmarthome.kl50.label = KL50
thing-type.tplinksmarthome.kl50.description = Kasa Filament Smart Bulb, Soft White
thing-type.tplinksmarthome.kl60.label = KL60
@ -150,3 +154,12 @@ channel-type.tplinksmarthome.switch-readonly.label = Switch
channel-type.tplinksmarthome.switch-readonly.description = Shows the switch state of the Smart Home device.
channel-type.tplinksmarthome.voltage.label = Voltage
channel-type.tplinksmarthome.voltage.description = Actual voltage usage.
# actions
actions.tplinksmarthome.send.label = Send Command
actions.tplinksmarthome.send.description = Sends the command, a json string, encrypted to a TP-Link device and decrypts the json response.
actions.tplinksmarthome.send.command.label = Command
actions.tplinksmarthome.send.command.description = The json string command to send to the TP-Link device.
actions.tplinksmarthome.send.response.label = Response
actions.tplinksmarthome.send.response.description = The decrypted json response returned by the TP-Link device.

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="kl400">
<label>KL400</label>
<description>TP-Link KL400 Kasa Smart Light Strip, Multicolour</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:tplinksmarthome: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="kl430">
<label>KL430</label>
<description>TP-Link KL430 Kasa Smart Light Strip, Multicolour</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:tplinksmarthome:device-bulb"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -34,6 +34,8 @@ 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.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.UnDefType;
/**
@ -173,7 +175,7 @@ public class BulbDeviceTest extends DeviceTestBase<BulbDevice> {
@Test
public void testUpdateChannelPower() {
assertEquals(new DecimalType(10.8), device.updateChannel(CHANNEL_UID_ENERGY_POWER, deviceState),
assertEquals(new QuantityType<>(10.8, Units.WATT), device.updateChannel(CHANNEL_UID_ENERGY_POWER, deviceState),
"Power values should be set");
}
}

View File

@ -14,7 +14,7 @@ package org.openhab.binding.tplinksmarthome.internal.device;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.lenient;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -63,7 +63,7 @@ public class DeviceTestBase<T extends SmartHomeDevice> {
*
* @throws IOException exception in case device not reachable
*/
protected DeviceTestBase(T device, String deviceStateFilename) throws IOException {
protected DeviceTestBase(final T device, final String deviceStateFilename) throws IOException {
this.device = device;
this.deviceStateFilename = deviceStateFilename;
configuration.ipAddress = "localhost";
@ -80,7 +80,7 @@ public class DeviceTestBase<T extends SmartHomeDevice> {
@BeforeEach
public void setUp() throws IOException {
when(socket.getOutputStream()).thenReturn(outputStream);
lenient().when(socket.getOutputStream()).thenReturn(outputStream);
deviceState = new DeviceState(ModelTestUtil.readJson(deviceStateFilename));
}
@ -91,11 +91,11 @@ public class DeviceTestBase<T extends SmartHomeDevice> {
* @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();
protected void setSocketReturnAssert(final String... responseFilenames) throws IOException {
final AtomicInteger index = new AtomicInteger();
doAnswer(i -> {
String stateResponse = ModelTestUtil.readJson(responseFilenames[index.getAndIncrement()]);
lenient().doAnswer(i -> {
final String stateResponse = ModelTestUtil.readJson(responseFilenames[index.getAndIncrement()]);
return new ByteArrayInputStream(CryptUtil.encryptWithLength(stateResponse));
}).when(socket).getInputStream();
@ -109,20 +109,20 @@ public class DeviceTestBase<T extends SmartHomeDevice> {
* @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 {
protected void assertInput(final 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();
protected void assertInput(final Function<String, String> jsonProcessor,
final Function<String, String> expectedProcessor, final String... filenames) throws IOException {
final AtomicInteger index = new AtomicInteger();
doAnswer(arg -> {
String json = jsonProcessor.apply(ModelTestUtil.readJson(filenames[index.get()]));
lenient().doAnswer(arg -> {
final String json = jsonProcessor.apply(ModelTestUtil.readJson(filenames[index.get()]));
byte[] input = (byte[]) arg.getArguments()[0];
final byte[] input = (byte[]) arg.getArguments()[0];
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(input)) {
String expectedString = expectedProcessor.apply(CryptUtil.decryptWithLength(inputStream));
final String expectedString = expectedProcessor.apply(CryptUtil.decryptWithLength(inputStream));
assertEquals(json, expectedString, filenames[index.get()]);
}
index.incrementAndGet();

View File

@ -0,0 +1,186 @@
/**
* Copyright (c) 2010-2022 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_BRIGHTNESS;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_COLOR;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_COLOR_TEMPERATURE;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_COLOR_TEMPERATURE_ABS;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_ENERGY_POWER;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_OTHER;
import static org.openhab.binding.tplinksmarthome.internal.ChannelUIDConstants.CHANNEL_UID_SWITCH;
import static org.openhab.binding.tplinksmarthome.internal.TPLinkSmartHomeThingType.KL430;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.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.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.UnDefType;
/**
* Test class for {@link BulbDevice} class.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
public class LightStripDeviceTest extends DeviceTestBase<LightStripDevice> {
private static final String DEVICE_OFF = "bulb_get_sysinfo_response_off";
public LightStripDeviceTest() throws IOException {
super(new LightStripDevice(KL430), "bulb_get_sysinfo_response_on");
}
@BeforeEach
@Override
public void setUp() throws IOException {
super.setUp();
}
@Test
public void testHandleCommandBrightness() throws IOException {
assertInput("kl430_set_brightness");
setSocketReturnAssert("kl430_set_brightness_response");
assertTrue(device.handleCommand(CHANNEL_UID_BRIGHTNESS, new PercentType(73)),
"Brightness channel should be handled");
}
@Test
public void testHandleCommandBrightnessOnOff() throws IOException {
assertInput("kl430_set_on");
setSocketReturnAssert("kl430_set_brightness_response");
assertTrue(device.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON),
"Brightness channel with OnOff state should be handled");
}
@Test
public void testHandleCommandColor() throws IOException {
assertInput("kl430_set_color");
setSocketReturnAssert("kl430_set_color_response");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR, new HSBType("115,75,73")),
"Color channel should be handled");
}
public void testHandleCommandColorBrightness() throws IOException {
assertInput("kl430_set_brightness");
setSocketReturnAssert("kl430_set_brightness_response");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR, new PercentType(33)),
"Color channel with Percentage state (=brightness) should be handled");
}
public void testHandleCommandColorOnOff() throws IOException {
assertInput("bulb_transition_light_state_on");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR, OnOffType.ON),
"Color channel with OnOff state should be handled");
}
@Test
public void testHandleCommandColorTemperature() throws IOException {
assertInput("kl430_set_colortemperature");
setSocketReturnAssert("kl430_set_colortemperature_response");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE, new PercentType(40)),
"Color temperature channel should be handled");
}
@Test
public void testHandleCommandColorTemperatureAbs() throws IOException {
assertInput("kl430_set_colortemperature");
setSocketReturnAssert("kl430_set_colortemperature_response");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE_ABS, new DecimalType(5100)),
"Color temperature channel should be handled");
}
@Test
public void testHandleCommandColorTemperatureOnOff() throws IOException {
assertInput("kl430_set_on");
setSocketReturnAssert("kl430_set_colortemperature_response");
assertTrue(device.handleCommand(CHANNEL_UID_COLOR_TEMPERATURE, OnOffType.ON),
"Color temperature channel with OnOff state should be handled");
}
// ---- Update ----
@Test
public void testUpdateChannelBrightnessOn() {
assertEquals(new PercentType(92), device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState),
"Brightness should be on");
}
@Test
public void testUpdateChannelBrightnessOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertEquals(PercentType.ZERO, device.updateChannel(CHANNEL_UID_BRIGHTNESS, deviceState),
"Brightness should be off");
}
@Test
public void testUpdateChannelColorOn() {
assertEquals(new HSBType("7,44,92"), device.updateChannel(CHANNEL_UID_COLOR, deviceState),
"Color should be on");
}
@Test
public void testUpdateChannelColorOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertEquals(new HSBType("7,44,0"), device.updateChannel(CHANNEL_UID_COLOR, deviceState),
"Color should be off");
}
@Test
public void testUpdateChannelSwitchOn() {
assertSame(OnOffType.ON, device.updateChannel(CHANNEL_UID_SWITCH, deviceState), "Switch should be on");
}
@Test
public void testUpdateChannelSwitchOff() throws IOException {
deviceState = new DeviceState(ModelTestUtil.readJson(DEVICE_OFF));
assertSame(OnOffType.OFF, device.updateChannel(CHANNEL_UID_SWITCH, deviceState), "Switch should be off");
}
@Test
public void testUpdateChannelColorTemperature() throws IOException {
assertInput("kl430_set_colortemperature");
assertEquals(new PercentType(2), device.updateChannel(CHANNEL_UID_COLOR_TEMPERATURE, deviceState),
"Color temperature should be set");
}
@Test
public void testUpdateChannelColorTemperatureAbs() throws IOException {
assertInput("kl430_set_colortemperature");
assertEquals(new DecimalType(2630), device.updateChannel(CHANNEL_UID_COLOR_TEMPERATURE_ABS, deviceState),
"Color temperature should be set");
}
@Test
public void testUpdateChannelOther() {
assertSame(UnDefType.UNDEF, device.updateChannel(CHANNEL_UID_OTHER, deviceState),
"Unknown channel should return UNDEF");
}
@Test
public void testUpdateChannelPower() {
assertEquals(new QuantityType<>(10.8, Units.WATT), device.updateChannel(CHANNEL_UID_ENERGY_POWER, deviceState),
"Power values should be set");
}
}

View File

@ -75,8 +75,8 @@ public class SmartHomeHandlerTest {
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()))
lenient().when(smartHomeDevice.getUpdateCommand()).thenReturn(Commands.getSysinfo());
lenient().when(connection.sendCommand(Commands.getSysinfo()))
.thenReturn(ModelTestUtil.readJson("plug_get_sysinfo_response"));
handler = new SmartHomeHandler(thing, smartHomeDevice, TPLinkSmartHomeThingType.HS100, discoveryService) {
@Override
@ -84,8 +84,8 @@ public class SmartHomeHandlerTest {
return connection;
}
};
when(smartHomeDevice.handleCommand(eq(CHANNEL_UID_SWITCH), any())).thenReturn(true);
when(callback.isChannelLinked(any())).thenReturn(true);
lenient().when(smartHomeDevice.handleCommand(eq(CHANNEL_UID_SWITCH), any())).thenReturn(true);
lenient().when(callback.isChannelLinked(any())).thenReturn(true);
handler.setCallback(callback);
}

View File

@ -0,0 +1,12 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"brightness": 73,
"on_off": 1,
"transition": 10
}
},
"context": {
"source": "12345668-1234-1234-1234-123456789012"
}
}

View File

@ -0,0 +1,19 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"on_off": 1,
"mode": "normal",
"groups": [
[
0,
31,
0,
0,
73,
4970
]
],
"err_code": 0
}
}
}

View File

@ -0,0 +1,15 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"color_temp": 0,
"hue": 115,
"saturation": 75,
"brightness": 73,
"on_off": 1,
"transition": 10
}
},
"context": {
"source": "12345668-1234-1234-1234-123456789012"
}
}

View File

@ -0,0 +1,19 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"on_off": 1,
"mode": "normal",
"groups": [
[
0,
31,
115,
75,
73,
0
]
],
"err_code": 0
}
}
}

View File

@ -0,0 +1,14 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"color_temp": 5100,
"hue": 0,
"saturation": 0,
"on_off": 1,
"transition": 10
}
},
"context": {
"source": "12345668-1234-1234-1234-123456789012"
}
}

View File

@ -0,0 +1,19 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"on_off": 1,
"mode": "normal",
"groups": [
[
0,
31,
0,
0,
73,
5100
]
],
"err_code": 0
}
}
}

View File

@ -0,0 +1,11 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"on_off": 1,
"transition": 10
}
},
"context": {
"source": "12345668-1234-1234-1234-123456789012"
}
}

View File

@ -0,0 +1,19 @@
{
"smartlife.iot.lightStrip": {
"set_light_state": {
"on_off": 1,
"mode": "normal",
"groups": [
[
0,
31,
0,
0,
100,
2630
]
],
"err_code": 0
}
}
}