diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/EcovacsBindingConstants.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/EcovacsBindingConstants.java index 3c85b30728bd4..1c4dd4249b965 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/EcovacsBindingConstants.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/EcovacsBindingConstants.java @@ -37,6 +37,7 @@ public class EcovacsBindingConstants { public static final String CLIENT_SECRET = "6c319b2a5cd3e66e39159c2e28f2fce9"; public static final String AUTH_CLIENT_KEY = "1520391491841"; public static final String AUTH_CLIENT_SECRET = "77ef58ce3afbe337da74aa8c5ab963a9"; + public static final String APP_KEY = "2ea31cf06e6711eaa0aff7b9558a534e"; // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_API = new ThingTypeUID(BINDING_ID, "ecovacsapi"); diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsApiConfiguration.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsApiConfiguration.java index 8208b3a5f1191..89b959a182dc1 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsApiConfiguration.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsApiConfiguration.java @@ -13,7 +13,7 @@ package org.openhab.binding.ecovacs.internal.api; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ecovacs.internal.api.util.MD5Util; +import org.openhab.binding.ecovacs.internal.api.util.HashUtil; /** * @author Johannes Ptaszyk - Initial contribution @@ -30,10 +30,12 @@ public final class EcovacsApiConfiguration { private final String clientSecret; private final String authClientKey; private final String authClientSecret; + private final String appKey; public EcovacsApiConfiguration(String deviceId, String username, String password, String continent, String country, - String language, String clientKey, String clientSecret, String authClientKey, String authClientSecret) { - this.deviceId = MD5Util.getMD5Hash(deviceId); + String language, String clientKey, String clientSecret, String authClientKey, String authClientSecret, + String appKey) { + this.deviceId = HashUtil.getMD5Hash(deviceId); this.username = username; this.password = password; this.continent = continent; @@ -43,6 +45,7 @@ public EcovacsApiConfiguration(String deviceId, String username, String password this.clientSecret = clientSecret; this.authClientKey = authClientKey; this.authClientSecret = authClientSecret; + this.appKey = appKey; } public String getDeviceId() { @@ -90,7 +93,7 @@ public String getRealm() { return "ecouser.net"; } - public String getPortalAUthRequestWith() { + public String getPortalAuthRequestWith() { return "users"; } @@ -110,12 +113,28 @@ public String getChannel() { return "google_play"; } + public String getAppId() { + return "ecovacs"; + } + + public String getAppPlatform() { + return "android"; + } + public String getAppCode() { return "global_e"; } public String getAppVersion() { - return "1.6.3"; + return "2.3.7"; + } + + public String getAppKey() { + return appKey; + } + + public String getAppUserAgent() { + return "EcovacsHome/2.3.7 (Linux; U; Android 5.1.1; A5010 Build/LMY48Z)"; } public String getDeviceType() { diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsDevice.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsDevice.java index 4bee37a2ef335..bda227ed70148 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsDevice.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/EcovacsDevice.java @@ -59,4 +59,6 @@ void connect(EventListener listener, ScheduledExecutorService scheduler) T sendCommand(IotDeviceCommand command) throws EcovacsApiException, InterruptedException; List getCleanLogs() throws EcovacsApiException, InterruptedException; + + Optional downloadCleanMapImage(CleanLogRecord record) throws EcovacsApiException, InterruptedException; } diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java index cfcf42fc34b24..a3f1603f2c87f 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiImpl.java @@ -59,14 +59,16 @@ import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.AbstractPortalIotCommandResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.Device; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.IotProduct; +import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalCleanLogRecord; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalCleanLogsResponse; +import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalCleanResultsResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalDeviceResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandJsonResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotCommandXmlResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalIotProductResponse; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse; import org.openhab.binding.ecovacs.internal.api.util.DataParsingException; -import org.openhab.binding.ecovacs.internal.api.util.MD5Util; +import org.openhab.binding.ecovacs.internal.api.util.HashUtil; import org.openhab.core.OpenHAB; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -116,8 +118,8 @@ PortalLoginResponse getLoginData() { private AccessData login() throws EcovacsApiException, InterruptedException { HashMap loginParameters = new HashMap<>(); loginParameters.put("account", configuration.getUsername()); - loginParameters.put("password", MD5Util.getMD5Hash(configuration.getPassword())); - loginParameters.put("requestId", MD5Util.getMD5Hash(String.valueOf(System.currentTimeMillis()))); + loginParameters.put("password", HashUtil.getMD5Hash(configuration.getPassword())); + loginParameters.put("requestId", HashUtil.getMD5Hash(String.valueOf(System.currentTimeMillis()))); loginParameters.put("authTimeZone", configuration.getTimeZone()); loginParameters.put("country", configuration.getCountry()); loginParameters.put("lang", configuration.getLanguage()); @@ -310,8 +312,7 @@ public T sendIotCommand(Device device, DeviceDescription desc, IotDeviceComm } } - public List fetchCleanLogs(Device device) - throws EcovacsApiException, InterruptedException { + public List fetchCleanLogs(Device device) throws EcovacsApiException, InterruptedException { PortalCleanLogsRequest data = new PortalCleanLogsRequest(createAuthData(), device.getDid(), device.getResource()); String url = EcovacsApiUrlFactory.getPortalLogUrl(configuration); @@ -324,12 +325,39 @@ public List fetchCleanLogs(Device device) return responseObj.records; } + public List fetchCleanResultsLog(Device device) + throws EcovacsApiException, InterruptedException { + String url = EcovacsApiUrlFactory.getPortalCleanResultsLogUrl(configuration); + Request request = createSignedAppRequest(url).param("auth", gson.toJson(createAuthData())) // + .param("channel", configuration.getChannel()) // + .param("did", device.getDid()) // + .param("defaultLang", "EN") // + .param("logType", "clean") // + .param("res", device.getResource()) // + .param("size", "20") // + .param("version", "v2"); + + ContentResponse response = executeRequest(request); + PortalCleanResultsResponse responseObj = handleResponse(response, PortalCleanResultsResponse.class); + if (!responseObj.wasSuccessful()) { + throw new EcovacsApiException("Fetching clean results failed"); + } + logger.trace("{}: Fetching cleaning results yields {} records", device.getName(), responseObj.records.size()); + return responseObj.records; + } + + public byte[] downloadCleanMapImage(String url, boolean useSigning) + throws EcovacsApiException, InterruptedException { + Request request = useSigning ? createSignedAppRequest(url) : httpClient.newRequest(url).method(HttpMethod.GET); + return executeRequest(request).getContent(); + } + private PortalAuthRequestParameter createAuthData() { PortalLoginResponse loginData = this.loginData; if (loginData == null) { throw new IllegalStateException("Not logged in"); } - return new PortalAuthRequestParameter(configuration.getPortalAUthRequestWith(), loginData.getUserId(), + return new PortalAuthRequestParameter(configuration.getPortalAuthRequestWith(), loginData.getUserId(), configuration.getRealm(), loginData.getToken(), configuration.getResource()); } @@ -371,7 +399,7 @@ private Request createAuthRequest(String url, String clientKey, String clientSec signOnText.append(clientSecret); signedRequestParameters.put("authAppkey", clientKey); - signedRequestParameters.put("authSign", MD5Util.getMD5Hash(signOnText.toString())); + signedRequestParameters.put("authSign", HashUtil.getMD5Hash(signOnText.toString())); Request request = httpClient.newRequest(url).method(HttpMethod.GET); signedRequestParameters.forEach(request::param); @@ -379,6 +407,27 @@ private Request createAuthRequest(String url, String clientKey, String clientSec return request; } + private Request createSignedAppRequest(String url) { + String timestamp = Long.toString(System.currentTimeMillis()); + String signContent = configuration.getAppId() + configuration.getAppKey() + timestamp; + PortalLoginResponse loginData = this.loginData; + if (loginData == null) { + throw new IllegalStateException("Not logged in"); + } + return httpClient.newRequest(url).method(HttpMethod.GET) + .header("Authorization", "Bearer " + loginData.getToken()) // + .header("token", loginData.getToken()) // + .header("appid", configuration.getAppId()) // + .header("plat", configuration.getAppPlatform()) // + .header("userid", loginData.getUserId()) // + .header("user-agent", configuration.getAppUserAgent()) // + .header("v", configuration.getAppVersion()) // + .header("country", configuration.getCountry()) // + .header("sign", HashUtil.getSHA256Hash(signContent)) // + .header("signType", "sha256") // + .param("et1", timestamp); + } + private Request createJsonRequest(String url, Object data) { return httpClient.newRequest(url).method(HttpMethod.POST).header(HttpHeader.CONTENT_TYPE, "application/json") .content(new StringContentProvider(gson.toJson(data))); diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiUrlFactory.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiUrlFactory.java index d50ab23340b96..98641b9b92200 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiUrlFactory.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsApiUrlFactory.java @@ -27,10 +27,11 @@ private EcovacsApiUrlFactory() { private static final String MAIN_URL_LOGIN_PATH = "/user/login"; - private static final String PORTAL_USERS_PATH = "/users/user.do"; - private static final String PORTAL_IOT_PRODUCT_PATH = "/pim/product/getProductIotMap"; - private static final String PORTAL_IOT_DEVMANAGER_PATH = "/iot/devmanager.do"; - private static final String PORTAL_LOG_PATH = "/lg/log.do"; + private static final String PORTAL_USERS_PATH = "/api/users/user.do"; + private static final String PORTAL_IOT_PRODUCT_PATH = "/api/pim/product/getProductIotMap"; + private static final String PORTAL_IOT_DEVMANAGER_PATH = "/api/iot/devmanager.do"; + private static final String PORTAL_LOG_PATH = "/api/lg/log.do"; + private static final String PORTAL_CLEAN_RESULTS_PATH = "/app/dln/api/log/clean_result/list"; public static String getLoginUrl(EcovacsApiConfiguration config) { return getMainUrl(config) + MAIN_URL_LOGIN_PATH; @@ -57,9 +58,13 @@ public static String getPortalLogUrl(EcovacsApiConfiguration config) { return getPortalUrl(config) + PORTAL_LOG_PATH; } + public static String getPortalCleanResultsLogUrl(EcovacsApiConfiguration config) { + return getPortalUrl(config) + PORTAL_CLEAN_RESULTS_PATH; + } + private static String getPortalUrl(EcovacsApiConfiguration config) { String continentSuffix = "cn".equalsIgnoreCase(config.getCountry()) ? "" : "-" + config.getContinent(); - return String.format("https://portal%1$s.ecouser.net/api", continentSuffix); + return String.format("https://portal%1$s.ecouser.net", continentSuffix); } private static String getMainUrl(EcovacsApiConfiguration config) { diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsIotMqDevice.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsIotMqDevice.java index 1bc866d36e15c..18e1e493e5b47 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsIotMqDevice.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsIotMqDevice.java @@ -34,6 +34,7 @@ import org.openhab.binding.ecovacs.internal.api.commands.GetFirmwareVersionCommand; import org.openhab.binding.ecovacs.internal.api.commands.IotDeviceCommand; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.Device; +import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalCleanLogRecord; import org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal.PortalLoginResponse; import org.openhab.binding.ecovacs.internal.api.model.CleanLogRecord; import org.openhab.binding.ecovacs.internal.api.model.DeviceCapability; @@ -103,12 +104,25 @@ public List getCleanLogs() throws EcovacsApiException, Interrupt if (desc.protoVersion == ProtocolVersion.XML) { logEntries = sendCommand(new GetCleanLogsCommand()).stream(); } else { - logEntries = api.fetchCleanLogs(device).stream().map(record -> new CleanLogRecord(record.timestamp, - record.duration, record.area, Optional.ofNullable(record.imageUrl), record.type)); + List log = hasCapability(DeviceCapability.USES_CLEAN_RESULTS_LOG_API) + ? api.fetchCleanResultsLog(device) + : api.fetchCleanLogs(device); + logEntries = log.stream().map(record -> new CleanLogRecord(record.timestamp, record.duration, record.area, + Optional.ofNullable(record.imageUrl), record.type)); } return logEntries.sorted((lhs, rhs) -> rhs.timestamp.compareTo(lhs.timestamp)).collect(Collectors.toList()); } + @Override + public Optional downloadCleanMapImage(CleanLogRecord record) + throws EcovacsApiException, InterruptedException { + if (record.mapImageUrl.isEmpty()) { + return Optional.empty(); + } + boolean needsSigning = hasCapability(DeviceCapability.USES_CLEAN_RESULTS_LOG_API); + return Optional.of(api.downloadCleanMapImage(record.mapImageUrl.get(), needsSigning)); + } + @Override public void connect(final EventListener listener, ScheduledExecutorService scheduler) throws EcovacsApiException, InterruptedException { diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsXmppDevice.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsXmppDevice.java index c5c365d90683c..992484c2fa8dd 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsXmppDevice.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/EcovacsXmppDevice.java @@ -153,6 +153,12 @@ public List getCleanLogs() throws EcovacsApiException, Interrupt return sendCommand(new GetCleanLogsCommand()); } + @Override + public Optional downloadCleanMapImage(CleanLogRecord record) + throws EcovacsApiException, InterruptedException { + return Optional.empty(); + } + @Override public void connect(final EventListener listener, final ScheduledExecutorService scheduler) throws EcovacsApiException { diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogRecord.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogRecord.java new file mode 100644 index 0000000000000..c90b62ae24f42 --- /dev/null +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogRecord.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2024 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.ecovacs.internal.api.impl.dto.response.portal; + +import org.openhab.binding.ecovacs.internal.api.model.CleanMode; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Danny Baumann - Initial contribution + */ +public class PortalCleanLogRecord { + @SerializedName("ts") + public final long timestamp; + + @SerializedName("last") + public final long duration; + + public final int area; + + public final String id; + + public final String imageUrl; + + public final CleanMode type; + + // more possible fields: + // aiavoid (int), aitypes (list of something), aiopen (int), aq (int), mapName (string), + // sceneName (string), triggerMode (int), powerMopType (int), enablePowerMop (int), cornerDeep (int) + + PortalCleanLogRecord(long timestamp, long duration, int area, String id, String imageUrl, CleanMode type) { + this.timestamp = timestamp; + this.duration = duration; + this.area = area; + this.id = id; + this.imageUrl = imageUrl; + this.type = type; + } +} diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogsResponse.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogsResponse.java index b39a46acb3b79..b1f13d0013e86 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogsResponse.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanLogsResponse.java @@ -14,48 +14,19 @@ import java.util.List; -import org.openhab.binding.ecovacs.internal.api.model.CleanMode; - import com.google.gson.annotations.SerializedName; /** * @author Johannes Ptaszyk - Initial contribution */ public class PortalCleanLogsResponse { - public static class LogRecord { - @SerializedName("ts") - public final long timestamp; - - @SerializedName("last") - public final long duration; - - public final int area; - - public final String id; - - public final String imageUrl; - - public final CleanMode type; - - // more possible fields: aiavoid (int), aitypes (list of something), stopReason (int) - - LogRecord(long timestamp, long duration, int area, String id, String imageUrl, CleanMode type) { - this.timestamp = timestamp; - this.duration = duration; - this.area = area; - this.id = id; - this.imageUrl = imageUrl; - this.type = type; - } - } - @SerializedName("logs") - public final List records; + public final List records; @SerializedName("ret") final String result; - PortalCleanLogsResponse(String result, List records) { + PortalCleanLogsResponse(String result, List records) { this.result = result; this.records = records; } diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanResultsResponse.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanResultsResponse.java new file mode 100644 index 0000000000000..969f6eb7f0b93 --- /dev/null +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/impl/dto/response/portal/PortalCleanResultsResponse.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2024 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.ecovacs.internal.api.impl.dto.response.portal; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Danny Baumann - Initial contribution + */ +public class PortalCleanResultsResponse { + @SerializedName("data") + public final List records; + + final int code; + final String message; + + PortalCleanResultsResponse(int code, String message, List records) { + this.code = code; + this.message = message; + this.records = records; + } + + public boolean wasSuccessful() { + return code == 0; + } +} diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/model/DeviceCapability.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/model/DeviceCapability.java index c9cea1422aa1d..262a3c2dbf107 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/model/DeviceCapability.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/model/DeviceCapability.java @@ -47,6 +47,8 @@ public enum DeviceCapability { TRUE_DETECT_3D, @SerializedName("unit_care_lifespan") UNIT_CARE_LIFESPAN, + @SerializedName("uses_clean_results_log_api") + USES_CLEAN_RESULTS_LOG_API, // implicit capabilities added in code EDGE_CLEANING, SPOT_CLEANING, diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/MD5Util.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/HashUtil.java similarity index 65% rename from bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/MD5Util.java rename to bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/HashUtil.java index 8ed83169aaeeb..1fb42521d8fe7 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/MD5Util.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/api/util/HashUtil.java @@ -23,30 +23,36 @@ * @author Johannes Ptaszyk - Initial contribution */ @NonNullByDefault -public class MD5Util { - private static final Logger LOGGER = LoggerFactory.getLogger(MD5Util.class); +public class HashUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(HashUtil.class); - private MD5Util() { + private HashUtil() { // Prevent instantiation of util class } public static String getMD5Hash(String input) { + return calculateHash("MD5", input); + } + + public static String getSHA256Hash(String input) { + return calculateHash("SHA-256", input); + } + + private static String calculateHash(String algorithm, String input) { MessageDigest md; try { - md = MessageDigest.getInstance("MD5"); + md = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { - LOGGER.error("Could not get MD5 MessageDigest instance", e); + LOGGER.error("Could not get {} MessageDigest instance", algorithm, e); return ""; } md.update(input.getBytes()); - byte[] hash = md.digest(); StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - if ((0xff & b) < 0x10) { - hexString.append("0").append(Integer.toHexString((0xFF & b))); - } else { - hexString.append(Integer.toHexString(0xFF & b)); + for (byte b : md.digest()) { + if ((b & 0xff) < 0x10) { + hexString.append("0"); } + hexString.append(Integer.toHexString(b & 0xff)); } return hexString.toString(); } diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsApiHandler.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsApiHandler.java index 7b4c2974db34d..69680572a340a 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsApiHandler.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsApiHandler.java @@ -122,7 +122,7 @@ private EcovacsApi createApi(String deviceIdSuffix, String country) { String deviceId = config.installId + deviceIdSuffix; org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration apiConfig = new org.openhab.binding.ecovacs.internal.api.EcovacsApiConfiguration( deviceId, config.email, config.password, config.continent, country, "EN", CLIENT_KEY, CLIENT_SECRET, - AUTH_CLIENT_KEY, AUTH_CLIENT_SECRET); + AUTH_CLIENT_KEY, AUTH_CLIENT_SECRET, APP_KEY); return EcovacsApi.create(httpClient, apiConfig); } diff --git a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsVacuumHandler.java b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsVacuumHandler.java index 32a5881ae2c81..0a24b2eaa58a5 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsVacuumHandler.java +++ b/bundles/org.openhab.binding.ecovacs/src/main/java/org/openhab/binding/ecovacs/internal/handler/EcovacsVacuumHandler.java @@ -80,7 +80,6 @@ import org.openhab.core.i18n.ConfigurationException; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; -import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -594,19 +593,11 @@ private void pollData() { if (device.hasCapability(DeviceCapability.MAPPING) && !lastDownloadedCleanMapUrl.equals(record.mapImageUrl)) { - updateState(CHANNEL_ID_LAST_CLEAN_MAP, record.mapImageUrl.flatMap(url -> { - // HttpUtil expects the server to return the correct MIME type, but Ecovacs' server sends - // 'application/octet-stream', so we have to set the correct MIME type by ourselves - @Nullable - RawType mapData = HttpUtil.downloadData(url, null, false, -1); - if (mapData != null) { - mapData = new RawType(mapData.getBytes(), "image/png"); - lastDownloadedCleanMapUrl = record.mapImageUrl; - } else { - logger.debug("{}: Downloading cleaning map {} failed", serialNumber, url); - } - return Optional.ofNullable((State) mapData); - }).orElse(UnDefType.NULL)); + Optional content = device.downloadCleanMapImage(record).map(bytes -> { + lastDownloadedCleanMapUrl = record.mapImageUrl; + return new RawType(bytes, "image/png"); + }); + updateState(CHANNEL_ID_LAST_CLEAN_MAP, content.orElse(UnDefType.NULL)); } } } diff --git a/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json b/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json index c585cf4baf1ef..fbd19dfc535fe 100644 --- a/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json +++ b/bundles/org.openhab.binding.ecovacs/src/main/resources/devices/supported_device_list.json @@ -354,125 +354,141 @@ "deviceClass": "vdehg6", "deviceClassLink": "fqxoiu" }, + { + "modelName": "DEEBOT N8 PRO+", + "deviceClass": "85as7h", + "deviceClassLink": "fqxoiu" + }, + { + "modelName": "DEEBOT N8 PRO+", + "deviceClass": "ifbw08", + "deviceClassLink": "fqxoiu" + }, + { "modelName": "DEEBOT T9+", "deviceClass": "lhbd50", - "deviceClassLink": "fqxoiu" + "protoVersion": "json_v2", + "usesMqtt": true, + "capabilities": [ + "mopping_system", + "main_brush", + "spot_area_cleaning", + "custom_area_cleaning", + "clean_speed_control", + "voice_reporting", + "read_network_info", + "unit_care_lifespan", + "true_detect_3d", + "mapping", + "auto_empty_station", + "uses_clean_results_log_api" + ] }, { "modelName": "DEEBOT T9+", "deviceClass": "um2ywg", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T9 AIVI", "deviceClass": "8kwdb4", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T9 AIVI", "deviceClass": "659yh8", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T9 AIVI Plus", "deviceClass": "kw9ayx", - "deviceClassLink": "fqxoiu" - }, - { - "modelName": "DEEBOT N8 PRO+", - "deviceClass": "85as7h", - "deviceClassLink": "fqxoiu" - }, - { - "modelName": "DEEBOT N8 PRO+", - "deviceClass": "ifbw08", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT N9+", "deviceClass": "a7lhb1", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT N9+", "deviceClass": "c2of2s", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1", "deviceClass": "3yqsch", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T10", "deviceClass": "jtmf04", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T10 PLUS", "deviceClass": "rss8xk", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T10 PLUS", "deviceClass": "p95mgv", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T10 TURBO", "deviceClass": "9s1s80", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T10 OMNI", "deviceClass": "lx3j7m", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1 OMNI", "deviceClass": "8bja83", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1 OMNI", "deviceClass": "1b23du", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1 OMNI", "deviceClass": "1vxt52", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1 TURBO", "deviceClass": "2o4lnm", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1 PLUS", "deviceClass": "n4gstt", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT X1e OMNI", "deviceClass": "bro5wu", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT T20 OMNI", "deviceClass": "p1jij8", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT N10 PLUS", "deviceClass": "umwv6z", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { "modelName": "DEEBOT N10 MAX+", "deviceClass": "clojes", - "deviceClassLink": "fqxoiu" + "deviceClassLink": "lhbd50" }, { @@ -576,7 +592,8 @@ "unit_care_lifespan", "true_detect_3d", "mapping", - "auto_empty_station" + "auto_empty_station", + "uses_clean_results_log_api" ] }, {