From 1518bb0020216a046f818c32524176e1fcab4f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Tue, 14 Feb 2023 12:09:31 +0100 Subject: [PATCH] [TapoControl] Adding P300 to the list of supported equipments (#14364) * Adding P300 to the list of supported equipments Signed-off-by: clinique --- .../org.openhab.binding.tapocontrol/README.md | 6 +- .../internal/api/TapoDeviceConnector.java | 119 +++++++++++---- .../internal/api/TapoDeviceHttpApi.java | 87 +++++++---- .../constants/TapoBindingSettings.java | 3 + .../constants/TapoThingConstants.java | 12 +- .../internal/device/TapoDevice.java | 49 ++++-- .../internal/device/TapoSmartPlug.java | 28 ++-- .../internal/helpers/PayloadBuilder.java | 6 +- .../internal/structures/TapoChild.java | 140 ++++++++++++++++++ .../internal/structures/TapoChildData.java | 41 +++++ .../internal/structures/TapoSubRequest.java | 44 ++++++ .../OH-INF/i18n/tapocontrol.properties | 10 ++ .../src/main/resources/OH-INF/thing/P300.xml | 23 +++ .../main/resources/OH-INF/thing/channels.xml | 19 +++ 14 files changed, 490 insertions(+), 97 deletions(-) create mode 100644 bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChild.java create mode 100644 bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChildData.java create mode 100644 bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoSubRequest.java create mode 100644 bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/P300.xml diff --git a/bundles/org.openhab.binding.tapocontrol/README.md b/bundles/org.openhab.binding.tapocontrol/README.md index 4df37cd0989d..640285fcbc11 100644 --- a/bundles/org.openhab.binding.tapocontrol/README.md +++ b/bundles/org.openhab.binding.tapocontrol/README.md @@ -12,6 +12,7 @@ The following Tapo-Devices are supported. For precise channel-description look a | | P105 | Smart Mini Socket | | EnergyMonitoring SmartPlug (Wi-Fi) | P110 | Energy Monitoring Smart Socket | | | P115 | Energy Monitoring Mini Smart Socket | +| Power Strip (Wi-Fi) | P300 | Smart Wi-Fi Power Strip - 3 sockets | | 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) | @@ -66,6 +67,9 @@ 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 | | | colorTemperature | Number | White-Color-Temp 2500-6500K | L510, L530, L610, L630, L900 | | | color | Color | Color | L530, L630, L900 | @@ -94,7 +98,7 @@ tapocontrol:L530:myTapoBridge:colorBulb "color-light" (tapocontrol:bri tapocontrol:L900:myTapoBridge:myLightStrip "light-strip" (tapocontrol:bridge:myTapoBridge) [ ipAddress="192.168.178.153", pollingInterval=30 ] Bridge tapocontrol:bridge:secondBridgeExample "Cloud-Login" [ username="youtoo@anyprovider.com", password="verysecret" ] { - Thing tapocontrol:P110:secondBridgeExample:mySocket "My-Socket" [ ipAddress="192.168.101.51", pollingInterval=30 ] + Thing P110 mySocket "My-Socket" [ ipAddress="192.168.101.51", pollingInterval=30 ] } ``` diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceConnector.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceConnector.java index a466e2d98525..c1c992338bdb 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceConnector.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceConnector.java @@ -15,22 +15,26 @@ import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*; import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorConstants.*; import static org.openhab.binding.tapocontrol.internal.constants.TapoThingConstants.*; -import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.*; +import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.jsonObjectToInt; import java.net.InetAddress; import java.util.HashMap; +import java.util.Objects; +import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.tapocontrol.internal.device.TapoBridgeHandler; import org.openhab.binding.tapocontrol.internal.device.TapoDevice; import org.openhab.binding.tapocontrol.internal.helpers.PayloadBuilder; import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler; +import org.openhab.binding.tapocontrol.internal.structures.TapoChild; +import org.openhab.binding.tapocontrol.internal.structures.TapoChildData; import org.openhab.binding.tapocontrol.internal.structures.TapoDeviceInfo; import org.openhab.binding.tapocontrol.internal.structures.TapoEnergyData; +import org.openhab.binding.tapocontrol.internal.structures.TapoSubRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonObject; /** @@ -41,12 +45,12 @@ */ @NonNullByDefault public class TapoDeviceConnector extends TapoDeviceHttpApi { + private final Logger logger = LoggerFactory.getLogger(TapoDeviceConnector.class); - private final String uid; - private final TapoDevice device; - private TapoDeviceInfo deviceInfo; - private TapoEnergyData energyData; - private Gson gson; + + private TapoDeviceInfo deviceInfo = new TapoDeviceInfo(); + private TapoEnergyData energyData = new TapoEnergyData(); + private TapoChildData childData = new TapoChildData(); private long lastQuery = 0L; private long lastSent = 0L; private long lastLogin = 0L; @@ -58,11 +62,6 @@ public class TapoDeviceConnector extends TapoDeviceHttpApi { */ public TapoDeviceConnector(TapoDevice device, TapoBridgeHandler bridgeThingHandler) { super(device, bridgeThingHandler); - this.device = device; - this.gson = new Gson(); - this.deviceInfo = new TapoDeviceInfo(); - this.energyData = new TapoEnergyData(); - this.uid = device.getThingUID().getAsString(); } /*********************************** @@ -111,7 +110,7 @@ public boolean login() { /** * send custom command to device - * + * * @param plBuilder Payloadbuilder with unencrypted payload */ public void sendCustomQuery(String queryMethod) { @@ -123,7 +122,7 @@ public void sendCustomQuery(String queryMethod) { /** * send custom command to device - * + * * @param plBuilder Payloadbuilder with unencrypted payload */ public void sendCustomPayload(PayloadBuilder plBuilder) { @@ -159,6 +158,27 @@ public void sendDeviceCommand(String name, Object value) { } } + /** + * send "set_device_info" command to child's device + * + * @param index of the child + * @param childProperty to modify + * @param value for the property + */ + public void sendChildCommand(Integer index, String childProperty, Object value) { + long now = System.currentTimeMillis(); + if (now > this.lastSent + TAPO_SEND_MIN_GAP_MS) { + this.lastSent = now; + getChild(index).ifPresent(child -> { + child.setDeviceOn(Boolean.valueOf((Boolean) value)); + TapoSubRequest request = new TapoSubRequest(child.getDeviceId(), DEVICE_CMD_SETINFO, child); + sendSecurePasstrhroug(GSON.toJson(request), request.method()); + }); + } else { + logger.debug("({}) command not sent because of min_gap: {}", uid, now + " <- " + lastSent); + } + } + /** * send multiple "set_device_info" commands to device * @@ -184,15 +204,16 @@ public void sendDeviceCommands(HashMap map) { } /** - * Query Info from Device adn refresh deviceInfo + * Query Info from Device and refresh deviceInfo */ public void queryInfo() { queryInfo(false); + queryChildDevices(); } /** - * Query Info from Device adn refresh deviceInfo - * + * Query Info from Device and refresh deviceInfo + * * @param ignoreGap ignore gap to last query. query anyway */ public void queryInfo(boolean ignoreGap) { @@ -212,6 +233,21 @@ public void queryInfo(boolean ignoreGap) { } } + /** + * Query Info from Child Devices and refresh deviceInfo + */ + @Override + public void queryChildDevices() { + logger.trace("({}) DeviceConnetor_queryChildDevices from '{}'", uid, deviceURL); + + /* create payload */ + PayloadBuilder plBuilder = new PayloadBuilder(); + plBuilder.method = DEVICE_CMD_CHILD_DEVICE_LIST; + String payload = plBuilder.getPayload(); + + sendSecurePasstrhroug(payload, DEVICE_CMD_CHILD_DEVICE_LIST); + } + /** * Get energy usage from device */ @@ -229,7 +265,7 @@ public void getEnergyUsage() { /** * SEND SECUREPASSTHROUGH * encprypt payload and send to device - * + * * @param payload payload sent to device * @param command command executed - this will handle result */ @@ -255,7 +291,7 @@ protected void sendSecurePasstrhroug(String payload, String command) { /** * Handle SuccessResponse (setDeviceInfo) - * + * * @param responseBody String with responseBody from device */ @Override @@ -270,9 +306,9 @@ protected void handleSuccessResponse(String responseBody) { } /** - * + * * handle JsonResponse (getDeviceInfo) - * + * * @param responseBody String with responseBody from device */ @Override @@ -290,7 +326,7 @@ protected void handleDeviceResult(String responseBody) { /** * handle JsonResponse (getEnergyData) - * + * * @param responseBody String with responseBody from device */ @Override @@ -305,9 +341,26 @@ protected void handleEnergyResult(String responseBody) { this.device.responsePasstrough(responseBody); } + /** + * handle JsonResponse (getChildDeviceList) + * + * @param responseBody String with responseBody from device + */ + @Override + protected void handleChildDevices(String responseBody) { + JsonObject jsnResult = getJsonFromResponse(responseBody); + if (jsnResult.has(CHILD_PROPERTY_START_INDEX)) { + this.childData = Objects.requireNonNull(GSON.fromJson(jsnResult, TapoChildData.class)); + this.device.setChildData(childData); + } else { + this.childData = new TapoChildData(); + } + this.device.responsePasstrough(responseBody); + } + /** * handle custom response - * + * * @param responseBody String with responseBody from device */ @Override @@ -317,7 +370,7 @@ protected void handleCustomResponse(String responseBody) { /** * handle error - * + * * @param te TapoErrorHandler */ @Override @@ -327,18 +380,18 @@ protected void handleError(TapoErrorHandler tapoError) { /** * get Json from response - * + * * @param responseBody * @return JsonObject with result */ private JsonObject getJsonFromResponse(String responseBody) { - JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class); + JsonObject jsonObject = GSON.fromJson(responseBody, JsonObject.class); /* get errocode (0=success) */ if (jsonObject != null) { Integer errorCode = jsonObjectToInt(jsonObject, "error_code"); if (errorCode == 0) { /* decrypt response */ - jsonObject = gson.fromJson(responseBody, JsonObject.class); + jsonObject = GSON.fromJson(responseBody, JsonObject.class); logger.trace("({}) received result: {}", uid, responseBody); if (jsonObject != null) { /* return result if set / else request was successful */ @@ -369,7 +422,7 @@ private JsonObject getJsonFromResponse(String responseBody) { /** * Check if device is online - * + * * @return true if device is online */ public Boolean isOnline() { @@ -378,7 +431,7 @@ public Boolean isOnline() { /** * Check if device is online - * + * * @param raiseError if true * @return true if device is online */ @@ -397,7 +450,7 @@ public Boolean isOnline(Boolean raiseError) { /** * IP-Adress - * + * * @return String ipAdress */ public String getIP() { @@ -406,7 +459,7 @@ public String getIP() { /** * PING IP Adress - * + * * @return true if ping successfull */ public Boolean pingDevice() { @@ -418,4 +471,8 @@ public Boolean pingDevice() { return false; } } + + private Optional getChild(int position) { + return childData.getChildDeviceList().stream().filter(child -> child.getPosition() == position).findFirst(); + } } diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceHttpApi.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceHttpApi.java index 53ad49a37867..3e58ead0d8b1 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceHttpApi.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/api/TapoDeviceHttpApi.java @@ -36,7 +36,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; /** @@ -47,11 +49,15 @@ */ @NonNullByDefault public class TapoDeviceHttpApi { + protected static final Gson GSON = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + private final Logger logger = LoggerFactory.getLogger(TapoDeviceHttpApi.class); - private final String uid; private final TapoCipher tapoCipher; private final TapoBridgeHandler bridge; - private Gson gson; + protected final String uid; + protected final TapoDevice device; + private String token = ""; private String cookie = ""; protected String deviceURL = ""; @@ -65,10 +71,9 @@ public class TapoDeviceHttpApi { public TapoDeviceHttpApi(TapoDevice device, TapoBridgeHandler bridgeThingHandler) { this.bridge = bridgeThingHandler; this.tapoCipher = new TapoCipher(); - this.gson = new Gson(); + this.device = device; this.uid = device.getThingUID().getAsString(); - String ipAddress = device.getIpAddress(); - setDeviceURL(ipAddress); + setDeviceURL(device.getIpAddress()); } /*********************************** @@ -79,7 +84,7 @@ public TapoDeviceHttpApi(TapoDevice device, TapoBridgeHandler bridgeThingHandler ************************************/ /** * handle SuccessResponse (setDeviceInfo) - * + * * @param responseBody String with responseBody from device */ protected void handleSuccessResponse(String responseBody) { @@ -87,7 +92,7 @@ protected void handleSuccessResponse(String responseBody) { /** * handle JsonResponse (getDeviceInfo) - * + * * @param responseBody String with responseBody from device */ protected void handleDeviceResult(String responseBody) { @@ -95,7 +100,7 @@ protected void handleDeviceResult(String responseBody) { /** * handle JsonResponse (getEnergyData) - * + * * @param responseBody String with responseBody from device */ protected void handleEnergyResult(String responseBody) { @@ -103,20 +108,35 @@ protected void handleEnergyResult(String responseBody) { /** * handle custom response - * + * * @param responseBody String with responseBody from device */ protected void handleCustomResponse(String responseBody) { } + /** + * handle JsonResponse (getChildDevices) + * + * @param responseBody String with responseBody from device + */ + protected void handleChildDevices(String responseBody) { + } + /** * handle error - * + * * @param te TapoErrorHandler */ protected void handleError(TapoErrorHandler tapoError) { } + /** + * refresh the list of child devices + * + */ + protected void queryChildDevices() { + } + /*********************************** * * LOGIN FUNCTIONS @@ -154,13 +174,13 @@ protected String createHandshake() { /** * return encrypted key from 'handshake' request - * + * * @param response ContentResponse from "handshake" method * @return */ private String getKeyFromResponse(ContentResponse response) { String rBody = response.getContentAsString(); - JsonObject jsonObj = gson.fromJson(rBody, JsonObject.class); + JsonObject jsonObj = GSON.fromJson(rBody, JsonObject.class); if (jsonObj != null) { logger.trace("({}) received awnser: {}", uid, rBody); return jsonObjectToString(jsonObj.getAsJsonObject("result"), "key"); @@ -173,7 +193,7 @@ private String getKeyFromResponse(ContentResponse response) { /** * return cookie from 'handshake' request - * + * * @param response ContentResponse from "handshake" metho * @return */ @@ -191,7 +211,7 @@ private String getCookieFromResponse(ContentResponse response) { /** * Query Token from device - * + * * @return String with token returned from device */ protected String queryToken() { @@ -223,7 +243,7 @@ protected String queryToken() { /** * get Token from "login"-request - * + * * @param response * @return */ @@ -236,7 +256,7 @@ private String getTokenFromResponse(@Nullable ContentResponse response) { logger.trace("({}) received result: {}", uid, decryptedResponse); /* get errocode (0=success) */ - JsonObject jsonObject = gson.fromJson(decryptedResponse, JsonObject.class); + JsonObject jsonObject = GSON.fromJson(decryptedResponse, JsonObject.class); if (jsonObject != null) { Integer errorCode = jsonObjectToInt(jsonObject, "error_code", ERR_JSON_DECODE_FAIL); if (errorCode == 0) { @@ -269,7 +289,7 @@ private String getTokenFromResponse(@Nullable ContentResponse response) { ************************************/ /** * SEND SYNCHRON HTTP-REQUEST - * + * * @param url url request is sent to * @param payload payload (String) to send * @return ContentResponse of request @@ -306,7 +326,7 @@ protected ContentResponse sendRequest(String url, String payload) { /** * SEND ASYNCHRONOUS HTTP-REQUEST * (don't wait for awnser with programm code) - * + * * @param url string url request is sent to * @param payload data-payload * @param command command executed - this will handle RepsonseType @@ -364,6 +384,9 @@ public void onComplete(Result result) { case DEVICE_CMD_CUSTOM: handleCustomResponse(rBody); break; + case DEVICE_CMD_CHILD_DEVICE_LIST: + handleChildDevices(rBody); + break; } } else { getErrorCode(rBody); @@ -378,7 +401,7 @@ public void onComplete(Result result) { /** * return error code from response - * + * * @param response * @return 0 if request was successfull */ @@ -397,13 +420,13 @@ protected Integer getErrorCode(@Nullable ContentResponse response) { /** * return error code from responseBody - * + * * @param responseBody * @return 0 if request was successfull */ protected Integer getErrorCode(String responseBody) { try { - JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class); + JsonObject jsonObject = GSON.fromJson(responseBody, JsonObject.class); /* get errocode (0=success) */ Integer errorCode = jsonObjectToInt(jsonObject, "error_code", ERR_JSON_DECODE_FAIL); if (errorCode == 0) { @@ -420,13 +443,13 @@ protected Integer getErrorCode(String responseBody) { /** * Check for JsonObject "errorcode" and if this is > 0 (no Error) - * + * * @param responseBody * @return true if is js errorcode > 0; false if there is no "errorcode" */ protected Boolean hasErrorCode(String responseBody) { if (isValidJson(responseBody)) { - JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class); + JsonObject jsonObject = GSON.fromJson(responseBody, JsonObject.class); /* get errocode (0=success) */ Integer errorCode = jsonObjectToInt(jsonObject, "error_code", ERR_JSON_DECODE_FAIL); if (errorCode > 0) { @@ -457,13 +480,13 @@ private Request setHeaders(Request httpRequest) { /** * Decrypt Response - * + * * @param responseBody encrypted string from response-body * @return String decrypted responseBody */ protected String decryptResponse(String responseBody) { try { - JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class); + JsonObject jsonObject = GSON.fromJson(responseBody, JsonObject.class); if (jsonObject != null) { String encryptedResponse = jsonObjectToString(jsonObject.getAsJsonObject("result"), "response"); return tapoCipher.decode(encryptedResponse); @@ -478,7 +501,7 @@ protected String decryptResponse(String responseBody) { /** * encrypt payload - * + * * @param payload * @return encrypted payload */ @@ -507,7 +530,7 @@ public void logout() { ************************************/ /** * Logged In - * + * * @return true if logged in */ public Boolean loggedIn() { @@ -516,7 +539,7 @@ public Boolean loggedIn() { /** * Logged In - * + * * @param raiseError if true * @return true if logged in */ @@ -540,7 +563,7 @@ public Boolean loggedIn(Boolean raiseError) { /** * Set new ipAddress - * + * * @param new ipAdress */ public void setDeviceURL(String ipAddress) { @@ -550,7 +573,7 @@ public void setDeviceURL(String ipAddress) { /** * Set new ipAdresss with token - * + * * @param ipAddress ipAddres of device * @param token token from login-ressult */ @@ -561,7 +584,7 @@ public void setDeviceURL(String ipAddress, String token) { /** * Set new token - * + * * @param deviceURL * @param token */ @@ -583,7 +606,7 @@ protected void unsetToken() { /** * Set new cookie - * + * * @param cookie */ protected void setCookie(String cookie) { diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoBindingSettings.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoBindingSettings.java index 7440ac7e080b..59c95b5d41c2 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoBindingSettings.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/constants/TapoBindingSettings.java @@ -52,5 +52,8 @@ public class TapoBindingSettings { public static final String DEVICE_CMD_GETINFO = "get_device_info"; public static final String DEVICE_CMD_SETINFO = "set_device_info"; public static final String DEVICE_CMD_GETENERGY = "get_energy_usage"; + public static final String DEVICE_CMD_CHILD_DEVICE_LIST = "get_child_device_list"; + public static final String DEVICE_CMD_CONTROL_CHILD = "control_child"; + public static final String DEVICE_CMD_MULTIPLE_REQ = "multipleRequest"; public static final String DEVICE_CMD_CUSTOM = "custom_command"; } 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 57c9219440e8..d10f51cefefb 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 @@ -12,7 +12,7 @@ */ package org.openhab.binding.tapocontrol.internal.constants; -import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*; +import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.BINDING_ID; import java.util.Collections; import java.util.Set; @@ -38,6 +38,7 @@ public class TapoThingConstants { public static final String DEVICE_P105 = "P105"; public static final String DEVICE_P110 = "P110"; public static final String DEVICE_P115 = "P115"; + public static final String DEVICE_P300 = "P300"; public static final String DEVICE_L510 = "L510"; public static final String DEVICE_L530 = "L530"; public static final String DEVICE_L610 = "L610"; @@ -50,6 +51,7 @@ public class TapoThingConstants { /*** LIST OF SUPPORTED DEVICE DESCRIPTIONS ***/ public static final String DEVICE_DESCRIPTION_BRIDGE = "TapoControl Cloud-Login"; public static final String DEVICE_DESCRIPTION_SMART_PLUG = "SmartPlug"; + public static final String DEVICE_DESCRIPTION_POWER_STRIP = "PowerStrip"; public static final String DEVICE_DESCRIPTION_WHITE_BULB = "White-Light-Bulb"; public static final String DEVICE_DESCRIPTION_COLOR_BULB = "Color-Light-Bulb"; public static final String DEVICE_DESCRIPTION_LIGHTSTRIP = "LightStrip"; @@ -60,6 +62,7 @@ public class TapoThingConstants { 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); public static final ThingTypeUID P115_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P115); + public static final ThingTypeUID P300_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_P300); 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); @@ -72,7 +75,7 @@ public class TapoThingConstants { /*** 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); + P110_THING_TYPE, P115_THING_TYPE, P300_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, @@ -85,6 +88,9 @@ public class TapoThingConstants { /*** THINGS WITH ENERGY DATA ***/ public static final Set SUPPORTED_ENERGY_DATA_UIDS = Set.of(P110_THING_TYPE, P115_THING_TYPE); + /*** THINGS WITH CHILDS DATA ***/ + public static final Set SUPPORTED_CHILDS_DATA_UIDS = Set.of(P300_THING_TYPE); + /*** THINGS WITH CHANNEL GROUPS ***/ public static final Set CHANNEL_GROUP_THING_SET = Collections .unmodifiableSet(Stream @@ -146,6 +152,8 @@ public class TapoThingConstants { public static final String ENERGY_PROPERTY_PAST7D = "past7d"; public static final String ENERGY_PROPERTY_PAST30D = "past30d"; public static final String ENERGY_PROPERTY_PAST1Y = "past1y"; + // childs management + public static final String CHILD_PROPERTY_START_INDEX = "start_index"; /*** DEVICE SETTINGS ***/ public static final Integer BULB_MIN_COLORTEMP = 2500; diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoDevice.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoDevice.java index 28b375e9d2f3..d169c0fd055c 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoDevice.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoDevice.java @@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tapocontrol.internal.api.TapoDeviceConnector; import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler; +import org.openhab.binding.tapocontrol.internal.structures.TapoChildData; import org.openhab.binding.tapocontrol.internal.structures.TapoDeviceConfiguration; import org.openhab.binding.tapocontrol.internal.structures.TapoDeviceInfo; import org.openhab.binding.tapocontrol.internal.structures.TapoEnergyData; @@ -131,7 +132,7 @@ private void activateDevice() { /** * CHECK SETTINGS - * + * * @return TapoErrorHandler with configuration-errors */ protected TapoErrorHandler checkSettings() { @@ -203,7 +204,7 @@ protected void startPollingScheduler() { /** * Stop scheduler - * + * * @param scheduler ScheduledFeature which schould be stopped */ protected void stopScheduler(@Nullable ScheduledFuture scheduler) { @@ -228,7 +229,7 @@ protected void pollingSchedulerAction() { ************************************/ /** * return device Error - * + * * @return */ public TapoErrorHandler getError() { @@ -237,7 +238,7 @@ public TapoErrorHandler getError() { /** * set device error - * + * * @param tapoError TapoErrorHandler-Object */ public void setError(TapoErrorHandler tapoError) { @@ -253,7 +254,7 @@ public void setError(TapoErrorHandler tapoError) { /*** * Check if ThingType is model - * + * * @param model * @return */ @@ -271,7 +272,7 @@ protected Boolean isThingModel(String model) { /** * CHECK IF RECEIVED DATA ARE FROM THE EXPECTED DEVICE * Compare MAC-Adress - * + * * @param deviceInfo * @return true if is the expected device */ @@ -315,7 +316,7 @@ public void queryDeviceInfo() { /** * query device Properties - * + * * @param ignoreGap ignore gap to last query. query anyway (force) */ public void queryDeviceInfo(boolean ignoreGap) { @@ -326,6 +327,10 @@ public void queryDeviceInfo(boolean ignoreGap) { if (SUPPORTED_ENERGY_DATA_UIDS.contains(getThing().getThingTypeUID())) { connector.getEnergyUsage(); } + // query childs data + if (SUPPORTED_CHILDS_DATA_UIDS.contains(getThing().getThingTypeUID())) { + connector.queryChildDevices(); + } } else { logger.debug("({}) tried to query DeviceInfo but not loggedIn", uid); connect(); @@ -334,7 +339,7 @@ public void queryDeviceInfo(boolean ignoreGap) { /** * SET DEVICE INFOs to device - * + * * @param deviceInfo */ public void setDeviceInfo(TapoDeviceInfo deviceInfo) { @@ -351,7 +356,7 @@ public void setDeviceInfo(TapoDeviceInfo deviceInfo) { /** * Set Device EnergyData to device - * + * * @param energyData */ public void setEnergyData(TapoEnergyData energyData) { @@ -363,9 +368,21 @@ public void setEnergyData(TapoEnergyData energyData) { getTimeType(energyData.getTodayRuntime(), Units.MINUTE)); } + /** + * Set Device Child data to device + * + * @param energyData + */ + public void setChildData(TapoChildData hostData) { + hostData.getChildDeviceList().forEach(child -> { + publishState(getChannelID(CHANNEL_GROUP_ACTUATOR, CHANNEL_OUTPUT + Integer.toString(child.getPosition())), + getOnOffType(child.getDeviceOn())); + }); + } + /** * Handle full responsebody received from connector - * + * * @param responseBody */ public void responsePasstrough(String responseBody) { @@ -373,10 +390,10 @@ public void responsePasstrough(String responseBody) { /** * UPDATE PROPERTIES - * + * * If only one property must be changed, there is also a convenient method * updateProperty(String name, String value). - * + * * @param TapoDeviceInfo */ protected void devicePropertiesChanged(TapoDeviceInfo deviceInfo) { @@ -392,7 +409,7 @@ protected void devicePropertiesChanged(TapoDeviceInfo deviceInfo) { /** * update channel state - * + * * @param channelID * @param value */ @@ -408,7 +425,7 @@ public void publishState(String channelID, State value) { /** * Connect (login) to device - * + * */ public Boolean connect() { deviceError.reset(); @@ -471,7 +488,7 @@ public String getIpAddress() { ************************************/ /** * Get ChannelID including group - * + * * @param group String channel-group * @param channel String channel-name * @return String channelID @@ -486,7 +503,7 @@ protected String getChannelID(String group, String channel) { /** * Get Channel from ChannelID - * + * * @param channelID String channelID * @return String channel-name */ diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartPlug.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartPlug.java index e6acff23df9f..50e09ab5fe4d 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartPlug.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/device/TapoSmartPlug.java @@ -37,7 +37,7 @@ public class TapoSmartPlug extends TapoDevice { /** * Constructor - * + * * @param thing Thing object representing device */ public TapoSmartPlug(Thing thing) { @@ -46,26 +46,30 @@ public TapoSmartPlug(Thing thing) { /** * handle command sent to device - * + * * @param channelUID channelUID command is sent to * @param command command to be sent */ @Override public void handleCommand(ChannelUID channelUID, Command command) { - Boolean refreshInfo = false; + boolean refreshInfo = false; + String id = channelUID.getIdWithoutGroup(); /* perform actions */ if (command instanceof RefreshType) { refreshInfo = true; - } else if (command == OnOffType.ON) { - connector.sendDeviceCommand(DEVICE_PROPERTY_ON, true); - refreshInfo = true; - } else if (command == OnOffType.OFF) { - connector.sendDeviceCommand(DEVICE_PROPERTY_ON, false); - refreshInfo = true; + } else if (command instanceof OnOffType) { + Boolean targetState = command == OnOffType.ON ? Boolean.TRUE : Boolean.FALSE; + if (CHANNEL_OUTPUT.equals(id)) { // Command is sent to the device output + connector.sendDeviceCommand(DEVICE_PROPERTY_ON, targetState); + refreshInfo = true; + } else if (id.startsWith(CHANNEL_OUTPUT)) { // Command is sent to a child's device output + Integer index = Integer.valueOf(id.replace(CHANNEL_OUTPUT, "")); + connector.sendChildCommand(index, DEVICE_PROPERTY_ON, targetState); + refreshInfo = true; + } } else { - logger.warn("({}) command type '{}' not supported for channel '{}'", uid, command.toString(), - channelUID.getId()); + logger.warn("({}) command type '{}' not supported for channel '{}'", uid, command, channelUID.getId()); } /* refreshInfo */ @@ -76,7 +80,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { /** * UPDATE PROPERTIES - * + * * @param TapoDeviceInfo */ @Override diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/helpers/PayloadBuilder.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/helpers/PayloadBuilder.java index fbb9d34d4784..810347e2a7c5 100644 --- a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/helpers/PayloadBuilder.java +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/helpers/PayloadBuilder.java @@ -55,7 +55,7 @@ public void addParameter(String name, Object value) { /** * Get JSON Payload (STRING) - * + * * @return String JSON-Payload */ public String getPayload() { @@ -66,7 +66,7 @@ public String getPayload() { /** * Get JSON Payload (JSON-Object) - * + * * @return JsonObject JSON-Payload */ public JsonObject getJsonPayload() { @@ -87,6 +87,6 @@ public JsonObject getJsonPayload() { * remove all parameters */ public void flushParameters(String command) { - this.parameters = new JsonObject(); + parameters = new JsonObject(); } } diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChild.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChild.java new file mode 100644 index 000000000000..d32f6a5ff747 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChild.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2023 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.structures; + +import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.MAC_DIVISION_CHAR; +import static org.openhab.binding.tapocontrol.internal.helpers.TapoUtils.formatMac; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Tapo Child Device Information class + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class TapoChild { + private String fwVer = ""; + private String hwVer = ""; + private String type = ""; + private String model = ""; + private String mac = ""; + private String category = ""; + private String deviceId = ""; + private boolean overheatStatus = false; + private int bindCount = 0; + private long onTime = 0; + private int slotNumber = 0; + private int position = 0; + private String nickname = ""; + private boolean deviceOn = false; + private String region = ""; + + /*********************************** + * + * GET VALUES + * + ************************************/ + + public String getFirmwareVersion() { + return fwVer; + } + + public String getHardwareVersion() { + return hwVer; + } + + public Boolean isOff() { + return !deviceOn; + } + + public Boolean isOn() { + return deviceOn; + } + + public String getMAC() { + return formatMac(mac, MAC_DIVISION_CHAR); + } + + public String getModel() { + return model.replace(" Series", ""); + } + + public String getNickname() { + return nickname; + } + + public Number getOnTime() { + return onTime; + } + + public String getRegion() { + return region; + } + + public String getRepresentationProperty() { + return getMAC(); + } + + public String getSerial() { + return deviceId; + } + + public String getType() { + return type; + } + + public String getFwVer() { + return fwVer; + } + + public String getHwVer() { + return hwVer; + } + + public String getMac() { + return mac; + } + + public String getCategory() { + return category; + } + + public String getDeviceId() { + return deviceId; + } + + public Boolean getOverheatStatus() { + return overheatStatus; + } + + public Integer getBindCount() { + return bindCount; + } + + public Integer getSlotNumber() { + return slotNumber; + } + + public Integer getPosition() { + return position; + } + + public Boolean getDeviceOn() { + return deviceOn; + } + + public void setDeviceOn(Boolean deviceOn) { + this.deviceOn = deviceOn; + } +} diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChildData.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChildData.java new file mode 100644 index 000000000000..457f02688847 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoChildData.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2023 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.structures; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Tapo-Child Structure Class + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class TapoChildData { + private int startIndex = 0; + private int sum = 0; + private List childDeviceList = List.of(); + + public int getStartIndex() { + return startIndex; + } + + public int getSum() { + return sum; + } + + public List getChildDeviceList() { + return childDeviceList; + } +} diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoSubRequest.java b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoSubRequest.java new file mode 100644 index 000000000000..ef022c699d93 --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/java/org/openhab/binding/tapocontrol/internal/structures/TapoSubRequest.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2023 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.structures; + +import static org.openhab.binding.tapocontrol.internal.constants.TapoBindingSettings.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * {@TapoSubRequest} holds data sent to device in order to act on a child + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public record TapoSubRequest(String method, Object params) { + private record ChildRequest(String device_id, @SerializedName("requestData") TapoSubRequest requestData) { + } + + private record SubMultiple(List requests) { + + private SubMultiple(String method, TapoChild params) { + this(List.of(new TapoSubRequest(method, params))); + } + } + + public TapoSubRequest(String deviceId, String method, TapoChild params) { + this(DEVICE_CMD_CONTROL_CHILD, new ChildRequest(deviceId, + new TapoSubRequest(DEVICE_CMD_MULTIPLE_REQ, new SubMultiple(method, params)))); + } +} 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 784f9e610f9d..8517d6ef6f00 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 @@ -27,6 +27,8 @@ thing-type.tapocontrol.P110.label = P110 SmartPlug thing-type.tapocontrol.P110.description = Tapo Smart Monitoring Wifi Plug thing-type.tapocontrol.P115.label = P115 SmartPlug thing-type.tapocontrol.P115.description = Tapo Smart Monitoring Wifi Plug +thing-type.tapocontrol.P300.label = P300 Power Strip +thing-type.tapocontrol.P300.description = Tapo Smart Wi-Fi Power Strip thing-type.tapocontrol.bridge.label = Cloud-Login thing-type.tapocontrol.bridge.description = Cloud Connector. Acts as device-bridge @@ -60,6 +62,14 @@ channel-group-type.tapocontrol.lightEffect.label = Lightning Effect channel-group-type.tapocontrol.lightEffect.description = Tapo Lightning Effects channel-group-type.tapocontrol.lightStrip.label = Color Light Strip channel-group-type.tapocontrol.lightStrip.description = Tapo Multicolor Smart Light Strip +channel-group-type.tapocontrol.powerStrip.label = SmartPlug +channel-group-type.tapocontrol.powerStrip.description = Tapo Smart Plug Power Outlet +channel-group-type.tapocontrol.powerStrip.channel.output1.label = Output Switch 1 +channel-group-type.tapocontrol.powerStrip.channel.output1.description = Switches the power state on/off of the first socket +channel-group-type.tapocontrol.powerStrip.channel.output2.label = Output Switch 2 +channel-group-type.tapocontrol.powerStrip.channel.output2.description = Switches the power state on/off of the second socket +channel-group-type.tapocontrol.powerStrip.channel.output3.label = Output Switch 3 +channel-group-type.tapocontrol.powerStrip.channel.output3.description = Switches the power state on/off of the third socket channel-group-type.tapocontrol.smartPlug.label = SmartPlug channel-group-type.tapocontrol.smartPlug.description = Tapo Smart Plug Power Outlet diff --git a/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/P300.xml b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/P300.xml new file mode 100644 index 000000000000..4eb2d689163d --- /dev/null +++ b/bundles/org.openhab.binding.tapocontrol/src/main/resources/OH-INF/thing/P300.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + Tapo Smart Wi-Fi Power Strip + + + + + 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 009179a5f481..c4c3c49b63ea 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 @@ -37,6 +37,25 @@ + + + Tapo Smart Plug Power Outlet + + + + Switches the power state on/off of the first socket + + + + Switches the power state on/off of the second socket + + + + Switches the power state on/off of the third socket + + + +