## Supported Things
-The following Tapo-Devices are supported
-
-### P100/P105 SmartPlug (Wi-Fi)
-
-* Power On/Off
-* Wi-Fi signal (SignalStrength)
-* On-Time (Time in seconds device is switched on)
-
-### P110/P115 EnergyMonitoring SmartPlug (Wi-Fi)
-
-* Power On/Off
-* Wi-Fi signal (SignalStrength)
-* On-Time (Time in seconds device is switched on)
-* actual PowerUsage (Watt)
-* today EnergyUsage (Wh)
-* today Runtime (Time in seconds device was on today)
-
-### L510(Series) dimmable SmartBulb (Wi-Fi)
-
-* Light On/Off
-* Brightnes (Dimmer) 0-100 %
-* ColorTemperature (Number) 2500-6500 K
-* Wi-Fi signal (SignalStrength)
-* On-Time (Time in seconds device is switched on)
-
-### L530(Series) MultiColor SmartBulb (Wi-Fi)
-
-* Light On/Off
-* Brightnes (Dimmer) 0-100 %
-* ColorTemperature (Number) 2500-6500 K
-* Color (Color)
-* Wi-Fi signal (SignalStrength)
-* On-Time (Time in seconds device is switched on)
-
-### L900/L920 LED-LightStrip (Wi-Fi)
-
-* Light On/Off
-* Brightnes (Dimmer) 0-100 %
-* ColorTemperature (Number) 2500-6500 K
-* Color (Color)
-* Wi-Fi signal (SignalStrength)
-* On-Time (Time in seconds device is switched on)
-
+The following Tapo-Devices are supported. For precise channel-description look at `channels-table` below
+
+| DeviceType | ThingType | Description |
+|------------------------------------|-------------|---------------------------------------------|
+| SmartPlug (Wi-Fi) | P100 | Smart Socket |
+| | P105 | Smart Mini Socket |
+| EnergyMonitoring SmartPlug (Wi-Fi) | P110 | Energy Monitoring Smart Socket |
+| | P115 | Energy Monitoring Mini Smart Socket |
+| Dimmable SmartBulb (Wi-Fi) | L510 | Dimmable White-Light Smart-Bulb (E27) |
+| | L610 | Dimmable White-Light Smart-Spot (GU10) |
+| MultiColor SmartBulb (Wi-Fi) | L530 | Multicolor Smart-Bulb (E27) |
+| | L630 | Multicolor Smart-Spot (GU10) |
+| MultiColor LightStrip (Wi-Fi) | L900 | Multicolor RGB Dimmable LightStrip (5m) |
+| | L920 | Multicolor RGB-IC ColorZone LightStrip (5m) |
+| | L930 | Multicolor RGBW-IC 50-Zone LightStrip (5m) |
## Prerequisites
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, L900, L920 |
-| | brightness | Dimmer | Brightness 0-100% | L510, L530, L900 |
-| | colorTemperature | Number | White-Color-Temp 2500-6500K | L510, L530, L900 |
-| | color | Color | Color | L530, L900 |
-| device | wifiSignal | system.signal-strength | WiFi-quality-level | P100, P105, P110, P115, L510, L530, L900, L920 |
-| | onTime | Number:Time | seconds output is on | P100, P105, P110, P115, L510, L530, L900, L920 |
-| 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 |
+| 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 |
+| | brightness | Dimmer | Brightness 0-100% | L510, L530, L610, L630, L900 |
+| | colorTemperature | Number | White-Color-Temp 2500-6500K | L510, L530, L610, L630, L900 |
+| | color | Color | Color | L530, L630, L900 |
+| 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 |
+| 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 |
## Channel Refresh
}
/**
- * deactivate
+ * activate
*/
@Override
public void activate() {
}
}
} catch (Exception e) {
- logger.debug("error handlling CloudDevices", e);
+ logger.debug("error handling CloudDevices", e);
}
}
public static final String DEVICE_P115 = "P115";
public static final String DEVICE_L510 = "L510";
public static final String DEVICE_L530 = "L530";
+ public static final String DEVICE_L610 = "L610";
+ public static final String DEVICE_L630 = "L630";
public static final String DEVICE_L900 = "L900";
public static final String DEVICE_L920 = "L920";
+ public static final String DEVICE_L930 = "L930";
public static final String DEVICE_UNIVERSAL = "Test_Device";
/*** LIST OF SUPPORTED DEVICE DESCRIPTIONS ***/
public static final ThingTypeUID P115_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P115);
public static final ThingTypeUID L510_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L510);
public static final ThingTypeUID L530_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L530);
+ public static final ThingTypeUID L610_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L610);
+ public static final ThingTypeUID L630_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L630);
public static final ThingTypeUID L900_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L900);
public static final ThingTypeUID L920_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L920);
+ public static final ThingTypeUID L930_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_L930);
public static final ThingTypeUID UNIVERSAL_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_UNIVERSAL);
/*** SET OF SUPPORTED UIDS ***/
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_UIDS = Set.of(BRIDGE_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_SMART_PLUG_UIDS = Set.of(P100_THING_TYPE, P105_THING_TYPE,
P110_THING_TYPE, P115_THING_TYPE);
- public static final Set<ThingTypeUID> SUPPORTED_WHITE_BULB_UIDS = Set.of(L510_THING_TYPE);
- public static final Set<ThingTypeUID> SUPPORTED_COLOR_BULB_UIDS = Set.of(L530_THING_TYPE);
- public static final Set<ThingTypeUID> SUPPORTED_LIGHT_STRIP_UIDS = Set.of(L900_THING_TYPE, L920_THING_TYPE);
+ public static final Set<ThingTypeUID> SUPPORTED_WHITE_BULB_UIDS = Set.of(L510_THING_TYPE, L610_THING_TYPE);
+ public static final Set<ThingTypeUID> SUPPORTED_COLOR_BULB_UIDS = Set.of(L530_THING_TYPE, L630_THING_TYPE);
+ public static final Set<ThingTypeUID> SUPPORTED_LIGHT_STRIP_UIDS = Set.of(L900_THING_TYPE, L920_THING_TYPE,
+ L930_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream
.of(SUPPORTED_BRIDGE_UIDS, SUPPORTED_SMART_PLUG_UIDS, SUPPORTED_WHITE_BULB_UIDS,
if (config.cloudDiscovery) {
logger.trace("{} discover devicelist from cloud", this.uid);
deviceList = getDeviceListCloud();
+ } else {
+ logger.info("{} Discovery disabled in bridge settings ", this.uid);
}
return deviceList;
}
protected void setColor(HSBType command) {
HashMap<String, Object> newState = new HashMap<>();
newState.put(DEVICE_PROPERTY_ON, true);
- newState.put(DEVICE_PROPERTY_HUE, command.getHue());
- newState.put(DEVICE_PROPERTY_SATURATION, command.getSaturation());
- newState.put(DEVICE_PROPERTY_BRIGHTNES, command.getBrightness());
+ newState.put(DEVICE_PROPERTY_HUE, command.getHue().intValue());
+ newState.put(DEVICE_PROPERTY_SATURATION, command.getSaturation().intValue());
+ newState.put(DEVICE_PROPERTY_BRIGHTNES, command.getBrightness().intValue());
connector.sendDeviceCommands(newState);
}
thing-type.tapocontrol.L510.description = Tapo Smart dimmable White-Light-Bulb
thing-type.tapocontrol.L530.label = L530 Series Color-Bulb
thing-type.tapocontrol.L530.description = Tapo Smart Multicolor Light-Bulb
+thing-type.tapocontrol.L610.label = L610 Series White-Spot
+thing-type.tapocontrol.L610.description = Tapo Smart dimmable White-Light-Spot
+thing-type.tapocontrol.L630.label = L630 Series Color-Spot
+thing-type.tapocontrol.L630.description = Tapo Smart Multicolor Light-Spot
thing-type.tapocontrol.L900.label = L900 LightStrip
thing-type.tapocontrol.L900.description = Tapo Smart LED-Lightstrip
thing-type.tapocontrol.L920.label = L920 LightStrip
thing-type.tapocontrol.L920.description = Tapo Smart Multicolor LED-Lightstrip
+thing-type.tapocontrol.L930.label = L930 LightStrip
+thing-type.tapocontrol.L930.description = Tapo Smart Multicolor LED-Lightstrip with ZoneControl
thing-type.tapocontrol.P100.label = P100 SmartPlug
thing-type.tapocontrol.P100.description = Tapo Smart Wifi Plug
thing-type.tapocontrol.P105.label = P105 SmartPlug
channel-group-type.tapocontrol.colorBulb.description = Tapo Multicolor Smart Light Bulb
channel-group-type.tapocontrol.deviceState.label = Device State
channel-group-type.tapocontrol.deviceState.description = Information about the device
+channel-group-type.tapocontrol.deviceStateS.label = Device State
+channel-group-type.tapocontrol.deviceStateS.description = Information about the device
channel-group-type.tapocontrol.energyMonitor.label = Energy Usage
channel-group-type.tapocontrol.energyMonitor.description = Energy and Power usage
channel-group-type.tapocontrol.lightBulb.label = Light Bulb
--- /dev/null
+<?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">
+
+ <!-- L610 THING-TYPE (WHITE-LIGHT-BULB) -->
+ <thing-type id="L610">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>L610 Series White-Spot</label>
+ <description>Tapo Smart dimmable White-Light-Spot</description>
+ <channel-groups>
+ <channel-group id="actuator" typeId="lightBulb"/>
+ <channel-group id="device" typeId="deviceStateS"/>
+ </channel-groups>
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:tapo:device"/>
+ </thing-type>
+</thing:thing-descriptions>
--- /dev/null
+<?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">
+
+ <!-- L630 THING-TYPE (COLOR-LIGHT-BULB) -->
+ <thing-type id="L630">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>L630 Series Color-Spot</label>
+ <description>Tapo Smart Multicolor Light-Spot</description>
+ <channel-groups>
+ <channel-group id="actuator" typeId="colorBulb"/>
+ <channel-group id="device" typeId="deviceStateS"/>
+ </channel-groups>
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:tapo:device"/>
+ </thing-type>
+</thing:thing-descriptions>
--- /dev/null
+<?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">
+
+ <!-- L930 THING-TYPE (Multicolor LED-Lightstrip) -->
+ <thing-type id="L930">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bridge"/>
+ </supported-bridge-type-refs>
+
+ <label>L930 LightStrip</label>
+ <description>Tapo Smart Multicolor LED-Lightstrip with ZoneControl</description>
+ <channel-groups>
+ <channel-group id="actuator" typeId="lightStrip"/>
+ <channel-group id="device" typeId="deviceState"/>
+ </channel-groups>
+ <representation-property>macAddress</representation-property>
+
+ <config-description-ref uri="thing-type:tapo:device"/>
+ </thing-type>
+</thing:thing-descriptions>
<!-- ############################### CHANNEL-GROUPS ############################### -->
<!-- CHANNEL GROUP TYPES -->
- <!--Device-Statuss Channel Type -->
+ <!--Device-Status Channel Type -->
<channel-group-type id="deviceState">
<label>Device State</label>
<description>Information about the device</description>
</channels>
</channel-group-type>
+ <!--Device-Status Channel Type (Small) -->
+ <channel-group-type id="deviceStateS">
+ <label>Device State</label>
+ <description>Information about the device</description>
+ <channels>
+ <channel id="wifiSignal" typeId="system.signal-strength"/>
+ <channel id="overheated" typeId="overheated"/>
+ </channels>
+ </channel-group-type>
+
<!--Actor Channel Type -->
<channel-group-type id="smartPlug">
<label>SmartPlug</label>
+++ /dev/null
-/**
- * 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.tapocontrol.internal;
-
-import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
-import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*;
-import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
-import org.openhab.binding.tapocontrol.internal.structures.TapoBridgeConfiguration;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-/**
- * Handler class for TAPO Smart Home thing discovery
- *
- * @author Christian Wild - Initial contribution
- */
-@NonNullByDefault
-public class TapoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
- private final Logger logger = LoggerFactory.getLogger(TapoDiscoveryService.class);
- protected @NonNullByDefault({}) TapoBridgeHandler bridge;
-
- /***********************************
- *
- * INITIALIZATION
- *
- ************************************/
-
- /**
- * INIT CLASS
- *
- * @param bridgeHandler
- */
- public TapoDiscoveryService() {
- super(SUPPORTED_THING_TYPES_UIDS, TAPO_DISCOVERY_TIMEOUT_S, false);
- }
-
- /**
- * deactivate
- */
- @Override
- public void activate() {
- TapoBridgeConfiguration config = bridge.getBridgeConfig();
- if (config.cloudDiscovery || config.udpDiscovery) {
- startBackgroundDiscovery();
- }
- }
-
- /**
- * deactivate
- */
- @Override
- public void deactivate() {
- super.deactivate();
- }
-
- @Override
- public void setThingHandler(@Nullable ThingHandler handler) {
- if (handler instanceof TapoBridgeHandler) {
- TapoBridgeHandler tapoBridge = (TapoBridgeHandler) handler;
- tapoBridge.setDiscoveryService(this);
- this.bridge = tapoBridge;
- }
- }
-
- @Override
- public @Nullable ThingHandler getThingHandler() {
- return this.bridge;
- }
-
- /***********************************
- *
- * SCAN HANDLING
- *
- ************************************/
-
- /**
- * Start scan manually
- */
- @Override
- public void startScan() {
- removeOlderResults(getTimestampOfLastScan());
- if (bridge != null) {
- JsonArray jsonArray = bridge.getDeviceList();
- handleCloudDevices(jsonArray);
- }
- }
-
- /***********************************
- *
- * handle Results
- *
- ************************************/
-
- /**
- * CREATE DISCOVERY RESULT
- * creates discoveryResult (Thing) from JsonObject got from Cloud
- *
- * @param device JsonObject with device information
- * @return DiscoveryResult-Object
- */
- public DiscoveryResult createResult(JsonObject device) {
- TapoBridgeHandler tapoBridge = this.bridge;
- String deviceModel = getDeviceModel(device);
- String label = getDeviceLabel(device);
- String deviceMAC = device.get(CLOUD_PROPERTY_MAC).getAsString();
- ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, deviceModel);
-
- /* create properties */
- Map<String, Object> properties = new HashMap<>();
- properties.put(Thing.PROPERTY_VENDOR, DEVICE_VENDOR);
- properties.put(Thing.PROPERTY_MAC_ADDRESS, formatMac(deviceMAC, MAC_DIVISION_CHAR));
- properties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.get(CLOUD_PROPERTY_FW).getAsString());
- properties.put(Thing.PROPERTY_HARDWARE_VERSION, device.get(CLOUD_PROPERTY_HW).getAsString());
- properties.put(Thing.PROPERTY_MODEL_ID, deviceModel);
- properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.get(CLOUD_PROPERTY_ID).getAsString());
-
- logger.debug("device {} discovered", deviceModel);
- if (tapoBridge != null) {
- ThingUID bridgeUID = tapoBridge.getUID();
- ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, deviceMAC);
- return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
- .withRepresentationProperty(DEVICE_REPRESENTATION_PROPERTY).withBridge(bridgeUID).withLabel(label)
- .build();
- } else {
- ThingUID thingUID = new ThingUID(BINDING_ID, deviceMAC);
- return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
- .withRepresentationProperty(DEVICE_REPRESENTATION_PROPERTY).withLabel(label).build();
- }
- }
-
- /**
- * work with result from get devices from cloud devices
- *
- * @param deviceList
- */
- protected void handleCloudDevices(JsonArray deviceList) {
- try {
- for (JsonElement deviceElement : deviceList) {
- if (deviceElement.isJsonObject()) {
- JsonObject device = deviceElement.getAsJsonObject();
- String deviceModel = getDeviceModel(device);
- ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, deviceModel);
-
- /* create thing */
- if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
- DiscoveryResult discoveryResult = createResult(device);
- thingDiscovered(discoveryResult);
- }
- }
- }
- } catch (Exception e) {
- logger.debug("error handlling CloudDevices", e);
- }
- }
-
- /**
- * GET DEVICEMODEL
- *
- * @param device JsonObject with deviceData
- * @return String with DeviceModel
- */
- protected String getDeviceModel(JsonObject device) {
- try {
- String deviceModel = device.get(CLOUD_PROPERTY_MODEL).getAsString();
- deviceModel = deviceModel.replaceAll("\\(.*\\)", ""); // replace (DE)
- deviceModel = deviceModel.replace("Tapo", "");
- deviceModel = deviceModel.replace("Series", "");
- deviceModel = deviceModel.trim();
- deviceModel = deviceModel.replace(" ", "_");
- return deviceModel;
- } catch (Exception e) {
- logger.debug("error getDeviceModel", e);
- return "";
- }
- }
-
- /**
- * GET DEVICE LABEL
- *
- * @param device JsonObject with deviceData
- * @return String with DeviceLabel
- */
- protected String getDeviceLabel(JsonObject device) {
- try {
- String deviceLabel = "";
- String deviceModel = getDeviceModel(device);
- ThingTypeUID deviceUID = new ThingTypeUID(BINDING_ID, deviceModel);
-
- if (SUPPORTED_SMART_PLUG_UIDS.contains(deviceUID)) {
- deviceLabel = DEVICE_DESCRIPTION_SMART_PLUG;
- } else if (SUPPORTED_WHITE_BULB_UIDS.contains(deviceUID)) {
- deviceLabel = DEVICE_DESCRIPTION_WHITE_BULB;
- } else if (SUPPORTED_COLOR_BULB_UIDS.contains(deviceUID)) {
- deviceLabel = DEVICE_DESCRIPTION_COLOR_BULB;
- }
- return DEVICE_VENDOR + " " + deviceModel + " " + deviceLabel;
- } catch (Exception e) {
- logger.debug("error getDeviceLabel", e);
- return "";
- }
- }
-}
--- /dev/null
+/**
+ * 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.tapocontrol.internal;
+
+import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*;
+import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*;
+import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler;
+import org.openhab.binding.tapocontrol.internal.structures.TapoBridgeConfiguration;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * Handler class for TAPO Smart Home thing discovery
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class TapoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+ private final Logger logger = LoggerFactory.getLogger(TapoDiscoveryService.class);
+ protected @NonNullByDefault({}) TapoBridgeHandler bridge;
+
+ /***********************************
+ *
+ * INITIALIZATION
+ *
+ ************************************/
+
+ /**
+ * INIT CLASS
+ *
+ * @param bridgeHandler
+ */
+ public TapoDiscoveryService() {
+ super(SUPPORTED_THING_TYPES_UIDS, TAPO_DISCOVERY_TIMEOUT_S, false);
+ }
+
+ /**
+ * activate
+ */
+ @Override
+ public void activate() {
+ TapoBridgeConfiguration config = bridge.getBridgeConfig();
+ if (config.cloudDiscovery || config.udpDiscovery) {
+ startBackgroundDiscovery();
+ }
+ }
+
+ /**
+ * deactivate
+ */
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof TapoBridgeHandler) {
+ TapoBridgeHandler tapoBridge = (TapoBridgeHandler) handler;
+ tapoBridge.setDiscoveryService(this);
+ this.bridge = tapoBridge;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.bridge;
+ }
+
+ /***********************************
+ *
+ * SCAN HANDLING
+ *
+ ************************************/
+
+ /**
+ * Start scan manually
+ */
+ @Override
+ public void startScan() {
+ removeOlderResults(getTimestampOfLastScan());
+ if (bridge != null) {
+ JsonArray jsonArray = bridge.getDeviceList();
+ handleCloudDevices(jsonArray);
+ }
+ }
+
+ /***********************************
+ *
+ * handle Results
+ *
+ ************************************/
+
+ /**
+ * CREATE DISCOVERY RESULT
+ * creates discoveryResult (Thing) from JsonObject got from Cloud
+ *
+ * @param device JsonObject with device information
+ * @return DiscoveryResult-Object
+ */
+ public DiscoveryResult createResult(JsonObject device) {
+ TapoBridgeHandler tapoBridge = this.bridge;
+ String deviceModel = getDeviceModel(device);
+ String label = getDeviceLabel(device);
+ String deviceMAC = device.get(CLOUD_PROPERTY_MAC).getAsString();
+ ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, deviceModel);
+
+ /* create properties */
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(Thing.PROPERTY_VENDOR, DEVICE_VENDOR);
+ properties.put(Thing.PROPERTY_MAC_ADDRESS, formatMac(deviceMAC, MAC_DIVISION_CHAR));
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.get(CLOUD_PROPERTY_FW).getAsString());
+ properties.put(Thing.PROPERTY_HARDWARE_VERSION, device.get(CLOUD_PROPERTY_HW).getAsString());
+ properties.put(Thing.PROPERTY_MODEL_ID, deviceModel);
+ properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.get(CLOUD_PROPERTY_ID).getAsString());
+
+ logger.debug("device {} discovered", deviceModel);
+ if (tapoBridge != null) {
+ ThingUID bridgeUID = tapoBridge.getUID();
+ ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, deviceMAC);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(DEVICE_REPRESENTATION_PROPERTY).withBridge(bridgeUID).withLabel(label)
+ .build();
+ } else {
+ ThingUID thingUID = new ThingUID(BINDING_ID, deviceMAC);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(DEVICE_REPRESENTATION_PROPERTY).withLabel(label).build();
+ }
+ }
+
+ /**
+ * work with result from get devices from cloud devices
+ *
+ * @param deviceList
+ */
+ protected void handleCloudDevices(JsonArray deviceList) {
+ try {
+ for (JsonElement deviceElement : deviceList) {
+ if (deviceElement.isJsonObject()) {
+ JsonObject device = deviceElement.getAsJsonObject();
+ String deviceModel = getDeviceModel(device);
+ ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, deviceModel);
+
+ /* create thing */
+ if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
+ DiscoveryResult discoveryResult = createResult(device);
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.debug("error handlling CloudDevices", e);
+ }
+ }
+
+ /**
+ * GET DEVICEMODEL
+ *
+ * @param device JsonObject with deviceData
+ * @return String with DeviceModel
+ */
+ protected String getDeviceModel(JsonObject device) {
+ try {
+ String deviceModel = device.get(CLOUD_PROPERTY_MODEL).getAsString();
+ deviceModel = deviceModel.replaceAll("\\(.*\\)", ""); // replace (DE)
+ deviceModel = deviceModel.replace("Tapo", "");
+ deviceModel = deviceModel.replace("Series", "");
+ deviceModel = deviceModel.trim();
+ deviceModel = deviceModel.replace(" ", "_");
+ return deviceModel;
+ } catch (Exception e) {
+ logger.debug("error getDeviceModel", e);
+ return "";
+ }
+ }
+
+ /**
+ * GET DEVICE LABEL
+ *
+ * @param device JsonObject with deviceData
+ * @return String with DeviceLabel
+ */
+ protected String getDeviceLabel(JsonObject device) {
+ try {
+ String deviceLabel = "";
+ String deviceModel = getDeviceModel(device);
+ ThingTypeUID deviceUID = new ThingTypeUID(BINDING_ID, deviceModel);
+
+ if (SUPPORTED_SMART_PLUG_UIDS.contains(deviceUID)) {
+ deviceLabel = DEVICE_DESCRIPTION_SMART_PLUG;
+ } else if (SUPPORTED_WHITE_BULB_UIDS.contains(deviceUID)) {
+ deviceLabel = DEVICE_DESCRIPTION_WHITE_BULB;
+ } else if (SUPPORTED_COLOR_BULB_UIDS.contains(deviceUID)) {
+ deviceLabel = DEVICE_DESCRIPTION_COLOR_BULB;
+ }
+ return DEVICE_VENDOR + " " + deviceModel + " " + deviceLabel;
+ } catch (Exception e) {
+ logger.debug("error getDeviceLabel", e);
+ return "";
+ }
+ }
+}
+++ /dev/null
-/**
- * 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.tapocontrol.internal.api;
-
-import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
-
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.SocketTimeoutException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.tapocontrol.internal.helpers.TapoCredentials;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-
-/**
- * Handler class for TAPO Smart Home device UDP-connections.
- * THIS IS FOR TESTING
- *
- * @author Christian Wild - Initial contribution
- */
-@NonNullByDefault
-public class TapoUDP {
- private final Logger logger = LoggerFactory.getLogger(TapoUDP.class);
- private static final Integer BROADCAST_TIMEOUT_MS = 5000;
- private static final Integer BROADCAST_DISCOVERY_PORT = 20002; // int
- private static final String BROADCAST_IP = "255.255.255.255";
- private static final String DISCOVERY_MESSAGE_KEY = "rsa_key";
- private static final String DISCOVERY_MESSAGE_START_BYTES = "0200000101e5110001cb8c577dd7deb8";
- private static final Integer BUFFER_SIZE = 501;
- private TapoCredentials credentials;
-
- public TapoUDP(TapoCredentials credentials) {
- this.credentials = credentials; // new TapoCredentials();
- }
-
- public JsonArray udpScan() {
- try {
- DatagramSocket udpSocket = new DatagramSocket();
- udpSocket.setSoTimeout(BROADCAST_TIMEOUT_MS);
- udpSocket.setBroadcast(true);
-
- /* create payload for handshake */
- String publicKey = credentials.getPublicKey();
- publicKey = generateOwnRSAKey(); // credentials.getPublicKey();
- JsonObject parameters = new JsonObject();
- JsonObject messageObject = new JsonObject();
- parameters.addProperty(DISCOVERY_MESSAGE_KEY, publicKey);
- messageObject.add("params", parameters);
-
- String discoveryMessage = messageObject.toString();
-
- byte[] startByte = hexStringToByteArray(DISCOVERY_MESSAGE_START_BYTES);
- byte[] message = discoveryMessage.getBytes("UTF-8");
- byte[] sendData = new byte[startByte.length + message.length];
- System.arraycopy(startByte, 0, sendData, 0, startByte.length);
- System.arraycopy(message, 0, sendData, startByte.length, message.length);
-
- DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
- InetAddress.getByName(BROADCAST_IP), BROADCAST_DISCOVERY_PORT);
-
- udpSocket.send(sendPacket);
-
- while (true) {
- // Wait for a response
- byte[] recvBuf = new byte[BUFFER_SIZE];
- DatagramPacket receivePacket;
- try {
- receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
- udpSocket.receive(receivePacket);
- } catch (SocketTimeoutException e) {
- udpSocket.close();
- return new JsonArray();
- } catch (Exception e) {
- udpSocket.close();
- return new JsonArray();
- }
-
- // Check if the message is correct
- String responseMessage = new String(receivePacket.getData(), "UTF-8").trim();
-
- if (responseMessage.length() == 0) {
- udpSocket.close();
- }
- String addressBC = receivePacket.getAddress().getHostAddress();
- gotDeviceAdress(addressBC);
- }
- } catch (Exception e) {
- // handle exception
- }
- return new JsonArray();
- }
-
- private void gotDeviceAdress(String ipAddress) {
- // handle exception
- }
-
- private String generateOwnRSAKey() {
- try {
- logger.trace("generating new keypair");
- KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
- instance.initialize(1536, new SecureRandom());
- KeyPair generateKeyPair = instance.generateKeyPair();
-
- String publicKey = new String(java.util.Base64.getMimeEncoder()
- .encode(((RSAPublicKey) generateKeyPair.getPublic()).getEncoded()));
- String privateKey = new String(java.util.Base64.getMimeEncoder()
- .encode(((RSAPrivateKey) generateKeyPair.getPrivate()).getEncoded()));
- logger.trace("new privateKey: '{}'", privateKey);
- logger.trace("new ublicKey: '{}'", publicKey);
-
- return String.format("-----BEGIN PUBLIC KEY-----%n%s%n-----END PUBLIC KEY-----%n", publicKey);
-
- } catch (Exception e) {
- // couldn't generate own rsa key
- return "";
- }
- }
-}
--- /dev/null
+/**
+ * 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.tapocontrol.internal.api;
+
+import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*;
+
+import java.util.Dictionary;
+import java.util.Set;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler class for TAPO Smart Home thing discovery over mDNS
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@Component(configurationPid = "discovery.tapocontrol")
+@NonNullByDefault
+public class TapoMDNS implements MDNSDiscoveryParticipant {
+ private final Logger logger = LoggerFactory.getLogger(TapoMDNS.class);
+ private boolean isAutoDiscoveryEnabled = true;
+
+ @Activate
+ protected void activate(ComponentContext componentContext) {
+ activateOrModifyService(componentContext);
+ }
+
+ @Modified
+ protected void modified(ComponentContext componentContext) {
+ activateOrModifyService(componentContext);
+ }
+
+ private void activateOrModifyService(ComponentContext componentContext) {
+ Dictionary<String, @Nullable Object> properties = componentContext.getProperties();
+ String autoDiscoveryPropertyValue = (String) properties
+ .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY);
+ if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) {
+ isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
+ }
+ }
+
+ @Override
+ public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+ return SUPPORTED_THING_TYPES_UIDS;
+ }
+
+ @Override
+ public String getServiceType() {
+ return "NULL";
+ }
+
+ @Override
+ public @Nullable ThingUID getThingUID(ServiceInfo service) {
+ ThingTypeUID thingTypeUID = getThingType(service);
+ if (thingTypeUID != null) {
+ String id = service.getPropertyString(PROPERTY_FAMILY); // device id
+ return new ThingUID(thingTypeUID, id);
+ }
+ return null;
+ }
+
+ private @Nullable ThingTypeUID getThingType(final ServiceInfo service) {
+ String model = service.getPropertyString(PROPERTY_FAMILY); // model
+ logger.debug("found Type: {}", model);
+ if (model == null) {
+ return null;
+ }
+ return L510_THING_TYPE;
+ }
+
+ @Override
+ public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+ if (isAutoDiscoveryEnabled) {
+ ThingUID uid = getThingUID(service);
+ if (uid != null) {
+ String host = service.getHostAddresses()[0];
+ int port = service.getPort();
+ logger.debug("device Found: {} {}", host, port);
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/**
+ * 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.tapocontrol.internal.api;
+
+import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.tapocontrol.internal.helpers.TapoCredentials;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+/**
+ * Handler class for TAPO Smart Home device UDP-connections.
+ * THIS IS FOR TESTING
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class TapoUDP {
+ private final Logger logger = LoggerFactory.getLogger(TapoUDP.class);
+ private static final Integer BROADCAST_TIMEOUT_MS = 5000;
+ private static final Integer BROADCAST_DISCOVERY_PORT = 20002; // int
+ private static final String BROADCAST_IP = "255.255.255.255";
+ private static final String DISCOVERY_MESSAGE_KEY = "rsa_key";
+ private static final String DISCOVERY_MESSAGE_START_BYTES = "0200000101e5110001cb8c577dd7deb8";
+ private static final Integer BUFFER_SIZE = 501;
+ private TapoCredentials credentials;
+
+ public TapoUDP(TapoCredentials credentials) {
+ this.credentials = credentials; // new TapoCredentials();
+ }
+
+ public JsonArray udpScan() {
+ try {
+ DatagramSocket udpSocket = new DatagramSocket();
+ udpSocket.setSoTimeout(BROADCAST_TIMEOUT_MS);
+ udpSocket.setBroadcast(true);
+
+ /* create payload for handshake */
+ String publicKey = credentials.getPublicKey();
+ publicKey = generateOwnRSAKey(); // credentials.getPublicKey();
+ JsonObject parameters = new JsonObject();
+ JsonObject messageObject = new JsonObject();
+ parameters.addProperty(DISCOVERY_MESSAGE_KEY, publicKey);
+ messageObject.add("params", parameters);
+
+ String discoveryMessage = messageObject.toString();
+
+ byte[] startByte = hexStringToByteArray(DISCOVERY_MESSAGE_START_BYTES);
+ byte[] message = discoveryMessage.getBytes("UTF-8");
+ byte[] sendData = new byte[startByte.length + message.length];
+ System.arraycopy(startByte, 0, sendData, 0, startByte.length);
+ System.arraycopy(message, 0, sendData, startByte.length, message.length);
+
+ DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
+ InetAddress.getByName(BROADCAST_IP), BROADCAST_DISCOVERY_PORT);
+
+ udpSocket.send(sendPacket);
+
+ while (true) {
+ // Wait for a response
+ byte[] recvBuf = new byte[BUFFER_SIZE];
+ DatagramPacket receivePacket;
+ try {
+ receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
+ udpSocket.receive(receivePacket);
+ } catch (SocketTimeoutException e) {
+ udpSocket.close();
+ return new JsonArray();
+ } catch (Exception e) {
+ udpSocket.close();
+ return new JsonArray();
+ }
+
+ // Check if the message is correct
+ String responseMessage = new String(receivePacket.getData(), "UTF-8").trim();
+
+ if (responseMessage.length() == 0) {
+ udpSocket.close();
+ }
+ String addressBC = receivePacket.getAddress().getHostAddress();
+ gotDeviceAdress(addressBC);
+ }
+ } catch (Exception e) {
+ // handle exception
+ }
+ return new JsonArray();
+ }
+
+ private void gotDeviceAdress(String ipAddress) {
+ // handle exception
+ }
+
+ private String generateOwnRSAKey() {
+ try {
+ logger.trace("generating new keypair");
+ KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
+ instance.initialize(1536, new SecureRandom());
+ KeyPair generateKeyPair = instance.generateKeyPair();
+
+ String publicKey = new String(java.util.Base64.getMimeEncoder()
+ .encode(((RSAPublicKey) generateKeyPair.getPublic()).getEncoded()));
+ String privateKey = new String(java.util.Base64.getMimeEncoder()
+ .encode(((RSAPrivateKey) generateKeyPair.getPrivate()).getEncoded()));
+ logger.trace("new privateKey: '{}'", privateKey);
+ logger.trace("new ublicKey: '{}'", publicKey);
+
+ return String.format("-----BEGIN PUBLIC KEY-----%n%s%n-----END PUBLIC KEY-----%n", publicKey);
+
+ } catch (Exception e) {
+ // couldn't generate own rsa key
+ return "";
+ }
+ }
+}