From: Christian Wild <40909464+wildcs@users.noreply.github.com> Date: Sat, 10 Dec 2022 15:53:30 +0000 (+0100) Subject: [tapocontrol] add L610, L630 Spot and L930 LightStrip (#13814) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=8d28085226ac74dc8182d412faa6cb5b61badd96;p=openhab-addons.git [tapocontrol] add L610, L630 Spot and L930 LightStrip (#13814) * fix color change bug* Signed-off-by: Christian Wild --- diff --git a/bundles/org.openhab.binding.tapocontrol/README.md b/bundles/org.openhab.binding.tapocontrol/README.md index 562c0f66a1..4df37cd098 100644 --- a/bundles/org.openhab.binding.tapocontrol/README.md +++ b/bundles/org.openhab.binding.tapocontrol/README.md @@ -4,49 +4,21 @@ This binding adds support to control Tapo (Copyright © TP-Link Corporation Limi ## 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 @@ -91,17 +63,17 @@ The thing 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, 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 diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java index f13bfc7f70..0e465af8c0 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java @@ -64,7 +64,7 @@ public class TapoDiscoveryService extends AbstractDiscoveryService implements Th } /** - * deactivate + * activate */ @Override public void activate() { @@ -178,7 +178,7 @@ public class TapoDiscoveryService extends AbstractDiscoveryService implements Th } } } catch (Exception e) { - logger.debug("error handlling CloudDevices", e); + logger.debug("error handling CloudDevices", e); } } diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoThingConstants.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoThingConstants.java index b2422668ea..60f2ec9009 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoThingConstants.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoThingConstants.java @@ -40,8 +40,11 @@ public class TapoThingConstants { 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 ***/ @@ -59,17 +62,21 @@ public class TapoThingConstants { 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 SUPPORTED_BRIDGE_UIDS = Set.of(BRIDGE_THING_TYPE); public static final Set SUPPORTED_SMART_PLUG_UIDS = Set.of(P100_THING_TYPE, P105_THING_TYPE, P110_THING_TYPE, P115_THING_TYPE); - public static final Set SUPPORTED_WHITE_BULB_UIDS = Set.of(L510_THING_TYPE); - public static final Set SUPPORTED_COLOR_BULB_UIDS = Set.of(L530_THING_TYPE); - public static final Set SUPPORTED_LIGHT_STRIP_UIDS = Set.of(L900_THING_TYPE, L920_THING_TYPE); + public static final Set SUPPORTED_WHITE_BULB_UIDS = Set.of(L510_THING_TYPE, L610_THING_TYPE); + public static final Set SUPPORTED_COLOR_BULB_UIDS = Set.of(L530_THING_TYPE, L630_THING_TYPE); + public static final Set SUPPORTED_LIGHT_STRIP_UIDS = Set.of(L900_THING_TYPE, L920_THING_TYPE, + L930_THING_TYPE); public static final Set SUPPORTED_THING_TYPES_UIDS = Collections .unmodifiableSet(Stream .of(SUPPORTED_BRIDGE_UIDS, SUPPORTED_SMART_PLUG_UIDS, SUPPORTED_WHITE_BULB_UIDS, diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoBridgeHandler.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoBridgeHandler.java index 832f1dfdf1..2f7296b074 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoBridgeHandler.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoBridgeHandler.java @@ -259,6 +259,8 @@ public class TapoBridgeHandler extends BaseBridgeHandler { 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; } diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartBulb.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartBulb.java index 985c005144..dd73822472 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartBulb.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartBulb.java @@ -127,9 +127,9 @@ public class TapoSmartBulb extends TapoDevice { protected void setColor(HSBType command) { HashMap 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); } diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/i18n/tapocontrol.properties b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/i18n/tapocontrol.properties index 2dc9379b14..1fc39a16f3 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/i18n/tapocontrol.properties +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/i18n/tapocontrol.properties @@ -9,10 +9,16 @@ 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 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 @@ -44,6 +50,8 @@ channel-group-type.tapocontrol.colorBulb.label = Color Light Bulb 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 diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L610.xml b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L610.xml new file mode 100644 index 0000000000..cc57394679 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L610.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + Tapo Smart dimmable White-Light-Spot + + + + + macAddress + + + + diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L630.xml b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L630.xml new file mode 100644 index 0000000000..1108af0dd3 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L630.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + Tapo Smart Multicolor Light-Spot + + + + + macAddress + + + + diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L930.xml b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L930.xml new file mode 100644 index 0000000000..2fe67041b7 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/L930.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + Tapo Smart Multicolor LED-Lightstrip with ZoneControl + + + + + macAddress + + + + diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/channels.xml index 04e6226dda..009179a5f4 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/channels.xml @@ -7,7 +7,7 @@ - + Information about the device @@ -18,6 +18,16 @@ + + + + Information about the device + + + + + + diff --git a/bundles/org.openhab.binding.tapocontrol/src/test/TapoDiscoveryService.java b/bundles/org.openhab.binding.tapocontrol/src/test/TapoDiscoveryService.java deleted file mode 100644 index 3c02a7c0d5..0000000000 --- a/bundles/org.openhab.binding.tapocontrol/src/test/TapoDiscoveryService.java +++ /dev/null @@ -1,231 +0,0 @@ -/** - * 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 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 ""; - } - } -} diff --git a/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java new file mode 100644 index 0000000000..c2e9994711 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/TapoDiscoveryService.java @@ -0,0 +1,231 @@ +/** + * 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 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 ""; + } + } +} diff --git a/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/api/TapoUDP.java b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/api/TapoUDP.java deleted file mode 100644 index f0abdadace..0000000000 --- a/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/api/TapoUDP.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * 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 ""; - } - } -} diff --git a/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoMDNS.java b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoMDNS.java new file mode 100644 index 0000000000..83fabceb0f --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoMDNS.java @@ -0,0 +1,107 @@ +/** + * 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 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 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; + } +} diff --git a/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoUDP.java b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoUDP.java new file mode 100644 index 0000000000..f0abdadace --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/test/java/org/openhab/binding/tapocontrol/internal/discovery/TapoUDP.java @@ -0,0 +1,138 @@ +/** + * 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 ""; + } + } +}