diff --git a/bundles/org.openhab.binding.tplinksmarthome/README.md b/bundles/org.openhab.binding.tplinksmarthome/README.md index 591748c1d52..cd820ce4102 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/README.md +++ b/bundles/org.openhab.binding.tplinksmarthome/README.md @@ -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 | diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Commands.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Commands.java index 55786b412da..8207e11806e 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Commands.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Commands.java @@ -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); + } } diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Connection.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Connection.java index c7a6d54a63c..1421e34c73d 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Connection.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/Connection.java @@ -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); diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeHandlerFactory.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeHandlerFactory.java index 9402e0f10da..d4366b2f328 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeHandlerFactory.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeHandlerFactory.java @@ -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; diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeThingType.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeThingType.java index 7550dee49e8..c4b35b71144 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeThingType.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/TPLinkSmartHomeThingType.java @@ -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. */ diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDevice.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDevice.java index ff4160f4f1e..9381114f632 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDevice.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDevice.java @@ -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; diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDevice.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDevice.java new file mode 100644 index 00000000000..796b8fd07c7 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDevice.java @@ -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))); + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandler.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandler.java index e3a3d4c25be..cb1a20db520 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandler.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandler.java @@ -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> 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) { diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/TPLinkSmartHomeActions.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/TPLinkSmartHomeActions.java new file mode 100644 index 00000000000..551edae9024 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/handler/TPLinkSmartHomeActions.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/model/SetLightState.java b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/model/SetLightState.java new file mode 100644 index 00000000000..235222a2ffe --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/java/org/openhab/binding/tplinksmarthome/internal/model/SetLightState.java @@ -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 + "}"; + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/i18n/tplinksmarthome.properties b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/i18n/tplinksmarthome.properties index 6447c4425cf..9fde9b44ea0 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/i18n/tplinksmarthome.properties +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/i18n/tplinksmarthome.properties @@ -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. diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL400.xml b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL400.xml new file mode 100644 index 00000000000..023b0048fd7 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL400.xml @@ -0,0 +1,24 @@ + + + + + + TP-Link KL400 Kasa Smart Light Strip, Multicolour + Lightbulb + + + + + + + + + + deviceId + + + + diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL430.xml b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL430.xml new file mode 100644 index 00000000000..b96857a6268 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/main/resources/OH-INF/thing/KL430.xml @@ -0,0 +1,24 @@ + + + + + + TP-Link KL430 Kasa Smart Light Strip, Multicolour + Lightbulb + + + + + + + + + + deviceId + + + + diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDeviceTest.java b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDeviceTest.java index bc7c3d97798..f5fdba6d023 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDeviceTest.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/BulbDeviceTest.java @@ -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 { @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"); } } diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/DeviceTestBase.java b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/DeviceTestBase.java index 951fdedbc99..e1a96460a01 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/DeviceTestBase.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/DeviceTestBase.java @@ -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 { * * @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 { @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 { * @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 { * @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 jsonProcessor, Function expectedProcessor, - String... filenames) throws IOException { - AtomicInteger index = new AtomicInteger(); + protected void assertInput(final Function jsonProcessor, + final Function 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(); diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDeviceTest.java b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDeviceTest.java new file mode 100644 index 00000000000..a157bb97017 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/device/LightStripDeviceTest.java @@ -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 { + + 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"); + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandlerTest.java b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandlerTest.java index 0b8ca68b8b1..5f8715947ff 100644 --- a/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandlerTest.java +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/java/org/openhab/binding/tplinksmarthome/internal/handler/SmartHomeHandlerTest.java @@ -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); } diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness.json new file mode 100644 index 00000000000..4ba58c7051b --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness.json @@ -0,0 +1,12 @@ +{ + "smartlife.iot.lightStrip": { + "set_light_state": { + "brightness": 73, + "on_off": 1, + "transition": 10 + } + }, + "context": { + "source": "12345668-1234-1234-1234-123456789012" + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness_response.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness_response.json new file mode 100644 index 00000000000..63a7ad56a6c --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_brightness_response.json @@ -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 + } + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color.json new file mode 100644 index 00000000000..624a1841278 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color.json @@ -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" + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color_response.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color_response.json new file mode 100644 index 00000000000..52b2edb7601 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_color_response.json @@ -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 + } + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature.json new file mode 100644 index 00000000000..f39d90f2b10 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature.json @@ -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" + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature_response.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature_response.json new file mode 100644 index 00000000000..a1994645183 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_colortemperature_response.json @@ -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 + } + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on.json new file mode 100644 index 00000000000..6f147751cd3 --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on.json @@ -0,0 +1,11 @@ +{ + "smartlife.iot.lightStrip": { + "set_light_state": { + "on_off": 1, + "transition": 10 + } + }, + "context": { + "source": "12345668-1234-1234-1234-123456789012" + } +} diff --git a/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on_response.json b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on_response.json new file mode 100644 index 00000000000..fa551a29a8b --- /dev/null +++ b/bundles/org.openhab.binding.tplinksmarthome/src/test/resources/org/openhab/binding/tplinksmarthome/internal/model/kl430_set_on_response.json @@ -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 + } + } +}