[tapocontrol] Add TP-Link HS200 Kasa Smart Wi-Fi Light Switch (#19924)

* Support TP-Link Smart Switches HS200

Signed-off-by: Simmon Yau <simmonyau@gmail.com>
This commit is contained in:
Simmon Yau
2026-01-01 10:37:06 +01:00
committed by GitHub
parent de599807ba
commit 9109e34ef5
9 changed files with 204 additions and 29 deletions
@@ -24,8 +24,9 @@ The following Tapo-Devices are supported. For precise channel-description look a
| Smart Contact Sensor (RF) | T110 | Window/Door Smart Contact Sensor |
| Smart Temperature Sensor (RF) | T310 | Temperature and Humidity Sensor |
| | T315 | Temperature and Humidity Sensor with Display |
| Smart Switch (RF) | S210 | Wall switch with 1 contact |
| Smart Switch (RF) | S220 | Wall switch with 2 contacts |
| Smart Switch (RF) | S210 | Wall switch with 1 contact |
| Smart Switch (RF) | S220 | Wall switch with 2 contacts |
| Smart Light Switch (Wi-Fi) | HS200 | Wall switch with 1 contact |
## Prerequisites
@@ -91,29 +92,29 @@ The things has the following configuration parameters:
All devices support some of the following channels:
| group | channel | type | description | things supporting this channel |
|-----------|----------------- |------------------------|-------------------------------------|------------------------------------------------------------------|
| actuator | output | Switch | Power device on or off | P100, P105, P110, P115, L510, L530, L610, L630, L900, L920, L930 |
| | output1 | Switch | Power socket 1 on or off | P300 |
| | output2 | Switch | Power socket 2 on or off | P300 |
| | output3 | Switch | Power socket 3 on or off | P300 |
| | brightness | Dimmer | Brightness 0-100% | L510, L530, L610, L630, L900, L920 |
| | colorTemperature | Number:Temperature | Color Temperature in Kelvin | L530 (2500 K .. 6500 K), L630 (2200 K .. 6500 K) |
| | color | Color | Color | L530, L630, L900, L920 |
| sensor | isOpen | Switch | Contact (Door/Window) is Open | T110 |
| | currentTemp | Number:Temperature | Current Temperature | T310, T315 |
| | currentHumidity | Number:Dimensionless | Current relative humidity in % | T310, T315 |
| effects | fxName | String | Active lightning effect | L530, L900, L920, L930 |
| device | wifiSignal | Number | WiFi-quality-level | P100, P105, P110, P115, L510, L530, L610, L630, L900, L920, L930 |
| | onTime | Number:Time | seconds output is on | P100, P105, P110, P115, L510, L530, L900, L920, L930 |
| | signalStrength | Number | RF-quality-level | T110 |
| | isOnline | Switch | Device is Online | T110 |
| | batteryLow | Switch | Battery of device is low | T110 |
| energy | actualPower | Number:Power | actual Power (Watt) | P110, P115 |
| | todayEnergyUsage | Number:Energy | used energy today (Wh) | P110, P115 |
| | todayRuntime | Number:Time | seconds output was on today | P110, P115 |
| alarm | alarmActive | Switch | Alarm is currntly active | H100 |
| | alarmSource | String | Source causes active alarm | H100 |
| group | channel | type | description | things supporting this channel |
|-----------|----------------- |------------------------|-------------------------------------|-------------------------------------------------------------------------|
| actuator | output | Switch | Power device on or off | P100, P105, P110, P115, L510, L530, L610, L630, L900, L920, L930, HS200 |
| | output1 | Switch | Power socket 1 on or off | P300 |
| | output2 | Switch | Power socket 2 on or off | P300 |
| | output3 | Switch | Power socket 3 on or off | P300 |
| | brightness | Dimmer | Brightness 0-100% | L510, L530, L610, L630, L900, L920 |
| | colorTemperature | Number:Temperature | Color Temperature in Kelvin | L530 (2500 K .. 6500 K), L630 (2200 K .. 6500 K) |
| | color | Color | Color | L530, L630, L900, L920 |
| sensor | isOpen | Switch | Contact (Door/Window) is Open | T110 |
| | currentTemp | Number:Temperature | Current Temperature | T310, T315 |
| | currentHumidity | Number:Dimensionless | Current relative humidity in % | T310, T315 |
| effects | fxName | String | Active lightning effect | L530, L900, L920, L930 |
| device | wifiSignal | Number | WiFi-quality-level | P100, P105, P110, P115, L510, L530, L610, L630, L900, L920, L930 |
| | onTime | Number:Time | seconds output is on | P100, P105, P110, P115, L510, L530, L900, L920, L930 |
| | signalStrength | Number | RF-quality-level | T110 |
| | isOnline | Switch | Device is Online | T110 |
| | batteryLow | Switch | Battery of device is low | T110 |
| energy | actualPower | Number:Power | actual Power (Watt) | P110, P115 |
| | todayEnergyUsage | Number:Energy | used energy today (Wh) | P110, P115 |
| | todayRuntime | Number:Time | seconds output was on today | P110, P115 |
| alarm | alarmActive | Switch | Alarm is currntly active | H100 |
| | alarmSource | String | Source causes active alarm | H100 |
## Channel Refresh
@@ -30,6 +30,7 @@ import org.openhab.binding.tapocontrol.internal.devices.wifi.TapoUniversalDevice
import org.openhab.binding.tapocontrol.internal.devices.wifi.bulb.TapoBulbHandler;
import org.openhab.binding.tapocontrol.internal.devices.wifi.hub.TapoHubHandler;
import org.openhab.binding.tapocontrol.internal.devices.wifi.lightstrip.TapoLightStripHandler;
import org.openhab.binding.tapocontrol.internal.devices.wifi.lightswitch.TapoLightSwitchHandler;
import org.openhab.binding.tapocontrol.internal.devices.wifi.socket.TapoSocketHandler;
import org.openhab.binding.tapocontrol.internal.devices.wifi.socket.TapoSocketStripHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
@@ -133,6 +134,8 @@ public class TapoControlHandlerFactory extends BaseThingHandlerFactory {
return new TapoWeatherSensorHandler(thing);
} else if (SUPPORTED_SMART_SWITCHES.contains(thingTypeUID)) {
return new TapoSmartSwitchHandler(thing);
} else if (SUPPORTED_LIGHT_SWITCH_UIDS.contains(thingTypeUID)) {
return new TapoLightSwitchHandler(thing);
} else if (thingTypeUID.equals(UNIVERSAL_THING_TYPE)) {
return new TapoUniversalDeviceHandler(thing);
}
@@ -35,6 +35,7 @@ public class TapoThingConstants {
/*** LIST OF SUPPORTED DEVICE NAMES ***/
public static final String DEVICE_BRIDGE = "bridge";
public static final String DEVICE_H100 = "H100";
public static final String DEVICE_HS200 = "HS200";
public static final String DEVICE_P100 = "P100";
public static final String DEVICE_P105 = "P105";
public static final String DEVICE_P110 = "P110";
@@ -66,10 +67,12 @@ public class TapoThingConstants {
public static final String DEVICE_DESCRIPTION_MOTION_SENSOR = "Motion-Sensor";
public static final String DEVICE_DESCRIPTION_TEMP_SENSOR = "Temperature-Sensor";
public static final String DEVICE_DESCRIPTION_SMART_SWITCH = "Smart-Switch";
public static final String DEVICE_DESCRIPTION_LIGHT_SWITCH = "Light-Switch";
/*** LIST OF SUPPORTED THING UIDS ***/
public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_BRIDGE);
public static final ThingTypeUID H100_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_H100);
public static final ThingTypeUID HS200_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_HS200);
public static final ThingTypeUID P100_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P100);
public static final ThingTypeUID P105_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P105);
public static final ThingTypeUID P110_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P110);
@@ -107,13 +110,15 @@ public class TapoThingConstants {
public static final Set<ThingTypeUID> SUPPORTED_MOTION_SENSORS = Set.of();
public static final Set<ThingTypeUID> SUPPORTED_WEATHER_SENSORS = Set.of(T310_THING_TYPE, T315_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_SMART_SWITCHES = Set.of(S210_THING_TYPE, S220_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_LIGHT_SWITCH_UIDS = Set.of(HS200_THING_TYPE);
/*** SET OF ALL SUPPORTED THINGS ***/
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(SUPPORTED_BRIDGE_UIDS, SUPPORTED_HUB_UIDS, SUPPORTED_SOCKET_UIDS, SUPPORTED_SOCKET_STRIP_UIDS,
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(SUPPORTED_BRIDGE_UIDS, SUPPORTED_HUB_UIDS, SUPPORTED_SOCKET_UIDS, SUPPORTED_SOCKET_STRIP_UIDS,
SUPPORTED_WHITE_BULB_UIDS, SUPPORTED_COLOR_BULB_UIDS, SUPPORTED_LIGHT_STRIP_UIDS,
SUPPORTED_SMART_CONTACTS, SUPPORTED_MOTION_SENSORS, SUPPORTED_WEATHER_SENSORS,
SUPPORTED_SMART_SWITCHES).flatMap(Set::stream).collect(Collectors.toSet()));
SUPPORTED_SMART_SWITCHES, SUPPORTED_LIGHT_SWITCH_UIDS)
.flatMap(Set::stream).collect(Collectors.toSet()));
/*** THINGS WITH ENERGY DATA ***/
public static final Set<ThingTypeUID> SUPPORTED_ENERGY_DATA_UIDS = Set.of(P110_THING_TYPE, P115_THING_TYPE);
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 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.tapocontrol.internal.devices.wifi.lightswitch;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tapocontrol.internal.devices.dto.TapoBaseDeviceData;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class TapoLightSwitchData extends TapoBaseDeviceData {
@SerializedName("device_on")
@Expose(serialize = true, deserialize = true)
private boolean deviceOn = false;
public void switchOnOff(boolean on) {
deviceOn = on;
}
public boolean isOn() {
return deviceOn;
}
public boolean isOff() {
return !deviceOn;
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2010-2025 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.tapocontrol.internal.devices.wifi.lightswitch;
import static org.openhab.binding.tapocontrol.internal.constants.TapoComConstants.*;
import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*;
import static org.openhab.binding.tapocontrol.internal.helpers.utils.TypeUtils.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.tapocontrol.internal.devices.wifi.TapoBaseDeviceHandler;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tapo Light Switch Handler
*
* @author Simmon Yau - Initial contribution
*/
@NonNullByDefault
public class TapoLightSwitchHandler extends TapoBaseDeviceHandler {
private final Logger logger = LoggerFactory.getLogger(TapoLightSwitchHandler.class);
private TapoLightSwitchData lightSwitchData = new TapoLightSwitchData();
/**
* Constructor
*
* @param thing Thing object representing device
*/
public TapoLightSwitchHandler(Thing thing) {
super(thing);
}
@Override
public void newDataResult(String queryCommand) {
super.newDataResult(queryCommand);
if (DEVICE_CMD_GETINFO.equals(queryCommand)) {
lightSwitchData = connector.getResponseData(TapoLightSwitchData.class);
updateChannels(lightSwitchData);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channel = channelUID.getIdWithoutGroup();
if (command instanceof RefreshType) {
queryDeviceData();
} else {
switch (channel) {
case CHANNEL_OUTPUT:
handleOnOffCommand(command);
break;
default:
logger.warn("({}) command type '{}' not supported for channel '{}'", uid, command,
channelUID.getId());
}
}
}
private void handleOnOffCommand(Command command) {
switchOnOff(command == OnOffType.ON ? Boolean.TRUE : Boolean.FALSE);
}
protected void switchOnOff(boolean on) {
lightSwitchData.switchOnOff(on);
connector.sendCommandAndQuery(lightSwitchData, true);
}
protected void updateChannels(TapoLightSwitchData deviceData) {
updateState(getChannelID(CHANNEL_GROUP_ACTUATOR, CHANNEL_OUTPUT), getOnOffType(deviceData.isOn()));
}
}
@@ -107,6 +107,7 @@ public class TapoUtils {
* @return mac address without any division chars
*/
public static String unformatMac(String mac) {
mac = mac.toLowerCase();
mac = mac.replace("-", "");
mac = mac.replace(":", "");
mac = mac.replace(".", "");
@@ -136,7 +137,12 @@ public class TapoUtils {
deviceModel = deviceModel.replace("Series", "");
deviceModel = deviceModel.trim();
deviceModel = deviceModel.replace(" ", "_");
deviceModel = deviceModel.substring(0, 4);
if (deviceModel.startsWith(DEVICE_HS200)) {
deviceModel = DEVICE_HS200;
} else {
// Keep legacy behavior of only first 4 characters
deviceModel = deviceModel.substring(0, 4);
}
return deviceModel;
} catch (Exception e) {
return "";
@@ -174,6 +180,8 @@ public class TapoUtils {
deviceLabel = DEVICE_DESCRIPTION_MOTION_SENSOR;
} else if (SUPPORTED_WEATHER_SENSORS.contains(deviceUID)) {
deviceLabel = DEVICE_DESCRIPTION_TEMP_SENSOR;
} else if (SUPPORTED_LIGHT_SWITCH_UIDS.contains(deviceUID)) {
deviceLabel = DEVICE_DESCRIPTION_LIGHT_SWITCH;
}
if (alias.length() > 0) {
return String.format("%s %s %s (%s)", DEVICE_VENDOR, deviceModel, deviceLabel, alias);
@@ -7,6 +7,8 @@ addon.tapocontrol.description = Control your TAPO-SmartHome Devices
thing-type.tapocontrol.H100.label = Tapo Hub H100
thing-type.tapocontrol.H100.description = Tapo SmartHub H100
thing-type.tapocontrol.HS200.label = HS200 Smart Light Switch
thing-type.tapocontrol.HS200.description = TP-Link HS200 Kasa Smart Wi-Fi Light Switch
thing-type.tapocontrol.L510.label = L510 Series White-Bulb
thing-type.tapocontrol.L510.description = Tapo Smart dimmable White-Light-Bulb
thing-type.tapocontrol.L530.label = L530 Series Color-Bulb
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tapocontrol"
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">
<!-- HS200 THING-TYPE (LIGHT SWITCH) -->
<thing-type id="HS200">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>HS200 Smart Light Switch</label>
<description>TP-Link HS200 Kasa Smart Wi-Fi Light Switch</description>
<semantic-equipment-tag>WallSwitch</semantic-equipment-tag>
<channel-groups>
<channel-group id="actuator" typeId="lightSwitch"/>
</channel-groups>
<representation-property>macAddress</representation-property>
<config-description-ref uri="thing-type:tapo:device"/>
</thing-type>
</thing:thing-descriptions>
@@ -145,4 +145,13 @@
</channels>
</channel-group-type>
<!-- Light Switch -->
<channel-group-type id="lightSwitch">
<label>Light Switch</label>
<description>Tapo Smart Light Switch</description>
<channels>
<channel id="output" typeId="lightOn"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>