mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[ecovacs] Add support for new API for fetching cleaning logs (#16524)
The existing cleaning logs API is only populated for devices older than the T9/N9 generation; all newer devices use a new API. Since the new API isn't populated for older devices, select the correct API depending on device type. Signed-off-by: Danny Baumann <dannybaumann@web.de> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
41b9a02dea
commit
13940df9bd
@ -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");
|
||||
|
@ -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 final class EcovacsApiConfiguration {
|
||||
this.clientSecret = clientSecret;
|
||||
this.authClientKey = authClientKey;
|
||||
this.authClientSecret = authClientSecret;
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
@ -90,7 +93,7 @@ public final class EcovacsApiConfiguration {
|
||||
return "ecouser.net";
|
||||
}
|
||||
|
||||
public String getPortalAUthRequestWith() {
|
||||
public String getPortalAuthRequestWith() {
|
||||
return "users";
|
||||
}
|
||||
|
||||
@ -110,12 +113,28 @@ public final class EcovacsApiConfiguration {
|
||||
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() {
|
||||
|
@ -59,4 +59,6 @@ public interface EcovacsDevice {
|
||||
<T> T sendCommand(IotDeviceCommand<T> command) throws EcovacsApiException, InterruptedException;
|
||||
|
||||
List<CleanLogRecord> getCleanLogs() throws EcovacsApiException, InterruptedException;
|
||||
|
||||
Optional<byte[]> downloadCleanMapImage(CleanLogRecord record) throws EcovacsApiException, InterruptedException;
|
||||
}
|
||||
|
@ -59,14 +59,16 @@ import org.openhab.binding.ecovacs.internal.api.impl.dto.response.main.ResponseW
|
||||
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 @@ public final class EcovacsApiImpl implements EcovacsApi {
|
||||
private AccessData login() throws EcovacsApiException, InterruptedException {
|
||||
HashMap<String, String> 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 final class EcovacsApiImpl implements EcovacsApi {
|
||||
}
|
||||
}
|
||||
|
||||
public List<PortalCleanLogsResponse.LogRecord> fetchCleanLogs(Device device)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
public List<PortalCleanLogRecord> 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 final class EcovacsApiImpl implements EcovacsApi {
|
||||
return responseObj.records;
|
||||
}
|
||||
|
||||
public List<PortalCleanLogRecord> 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 @@ public final class EcovacsApiImpl implements EcovacsApi {
|
||||
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 @@ public final class EcovacsApiImpl implements EcovacsApi {
|
||||
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)));
|
||||
|
@ -27,10 +27,11 @@ public final class 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 final class EcovacsApiUrlFactory {
|
||||
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) {
|
||||
|
@ -34,6 +34,7 @@ import org.openhab.binding.ecovacs.internal.api.commands.GetCleanLogsCommand;
|
||||
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 class EcovacsIotMqDevice implements EcovacsDevice {
|
||||
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<PortalCleanLogRecord> 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<byte[]> 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 {
|
||||
|
@ -153,6 +153,12 @@ public class EcovacsXmppDevice implements EcovacsDevice {
|
||||
return sendCommand(new GetCleanLogsCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<byte[]> downloadCleanMapImage(CleanLogRecord record)
|
||||
throws EcovacsApiException, InterruptedException {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(final EventListener listener, final ScheduledExecutorService scheduler)
|
||||
throws EcovacsApiException {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -14,48 +14,19 @@ package org.openhab.binding.ecovacs.internal.api.impl.dto.response.portal;
|
||||
|
||||
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<LogRecord> records;
|
||||
public final List<PortalCleanLogRecord> records;
|
||||
|
||||
@SerializedName("ret")
|
||||
final String result;
|
||||
|
||||
PortalCleanLogsResponse(String result, List<LogRecord> records) {
|
||||
PortalCleanLogsResponse(String result, List<PortalCleanLogRecord> records) {
|
||||
this.result = result;
|
||||
this.records = records;
|
||||
}
|
||||
|
@ -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<PortalCleanLogRecord> records;
|
||||
|
||||
final int code;
|
||||
final String message;
|
||||
|
||||
PortalCleanResultsResponse(int code, String message, List<PortalCleanLogRecord> records) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return code == 0;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -23,30 +23,36 @@ import org.slf4j.LoggerFactory;
|
||||
* @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();
|
||||
}
|
@ -122,7 +122,7 @@ public class EcovacsApiHandler extends BaseBridgeHandler {
|
||||
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);
|
||||
}
|
||||
|
@ -80,7 +80,6 @@ import org.openhab.binding.ecovacs.internal.util.StateOptionMapping;
|
||||
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 @@ public class EcovacsVacuumHandler extends BaseThingHandler implements EcovacsDev
|
||||
|
||||
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<State> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,31 +354,6 @@
|
||||
"deviceClass": "vdehg6",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9+",
|
||||
"deviceClass": "lhbd50",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9+",
|
||||
"deviceClass": "um2ywg",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI",
|
||||
"deviceClass": "8kwdb4",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI",
|
||||
"deviceClass": "659yh8",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI Plus",
|
||||
"deviceClass": "kw9ayx",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT N8 PRO+",
|
||||
"deviceClass": "85as7h",
|
||||
@ -389,90 +364,131 @@
|
||||
"deviceClass": "ifbw08",
|
||||
"deviceClassLink": "fqxoiu"
|
||||
},
|
||||
|
||||
{
|
||||
"modelName": "DEEBOT T9+",
|
||||
"deviceClass": "lhbd50",
|
||||
"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": "lhbd50"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI",
|
||||
"deviceClass": "8kwdb4",
|
||||
"deviceClassLink": "lhbd50"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI",
|
||||
"deviceClass": "659yh8",
|
||||
"deviceClassLink": "lhbd50"
|
||||
},
|
||||
{
|
||||
"modelName": "DEEBOT T9 AIVI Plus",
|
||||
"deviceClass": "kw9ayx",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user