diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java index 3bda9afc25110..a81c3a793a70c 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoBindingConstants.java @@ -100,6 +100,8 @@ public final class MiIoBindingConstants { public static final String CHANNEL_HISTORY_DURATION = "cleaning#last_clean_duration"; public static final String CHANNEL_HISTORY_ERROR = "cleaning#last_clean_error"; public static final String CHANNEL_HISTORY_FINISH = "cleaning#last_clean_finish"; + public static final String CHANNEL_HISTORY_FINISHREASON = "cleaning#last_clean_finish_reason"; + public static final String CHANNEL_HISTORY_DUSTCOLLECTION = "cleaning#last_clean_dustcollection_status"; public static final String CHANNEL_HISTORY_RECORD = "cleaning#last_clean_record"; public static final String CHANNEL_VACUUM_MAP = "cleaning#map"; diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java index c6d8758402177..c8f1d151e5907 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/CloudConnector.java @@ -16,8 +16,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -33,7 +36,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; /** * The {@link CloudConnector} is responsible for connecting OH to the Xiaomi cloud communication. @@ -46,14 +53,15 @@ public class CloudConnector { private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toMillis(60); - private enum DeviceListState { + private enum CloudListState { FAILED, STARTING, REFRESHING, AVAILABLE, } - private volatile DeviceListState deviceListState = DeviceListState.STARTING; + private volatile CloudListState deviceListState = CloudListState.STARTING; + private volatile CloudListState homeListState = CloudListState.STARTING; private String username = ""; private String password = ""; @@ -64,19 +72,22 @@ private enum DeviceListState { private @Nullable MiCloudConnector cloudConnector; private final Logger logger = LoggerFactory.getLogger(CloudConnector.class); + private ConcurrentHashMap<@NonNull String, @NonNull HomeListDTO> homeLists = new ConcurrentHashMap<>(); + private static final Gson GSON = new GsonBuilder().serializeNulls().create(); + private ExpiringCache logonCache = new ExpiringCache(CACHE_EXPIRY, () -> { return logon(); }); private ExpiringCache refreshDeviceList = new ExpiringCache(CACHE_EXPIRY, () -> { - if (deviceListState == DeviceListState.FAILED && !isConnected()) { + if (deviceListState == CloudListState.FAILED && !isConnected()) { return ("Could not connect to Xiaomi cloud"); } final @Nullable MiCloudConnector cl = this.cloudConnector; if (cl == null) { return ("Could not connect to Xiaomi cloud"); } - deviceListState = DeviceListState.REFRESHING; + deviceListState = CloudListState.REFRESHING; deviceList.clear(); for (String server : country.split(",")) { try { @@ -85,10 +96,48 @@ private enum DeviceListState { logger.debug("Parsing error getting devices: {}", e.getMessage()); } } - deviceListState = DeviceListState.AVAILABLE; + deviceListState = CloudListState.AVAILABLE; + return "done";// deviceList; + }); + + private ExpiringCache refreshHomeList = new ExpiringCache(CACHE_EXPIRY, () -> { + if (homeListState == CloudListState.FAILED && !isConnected()) { + return ("Could not connect to Xiaomi cloud"); + } + final @Nullable MiCloudConnector cl = this.cloudConnector; + if (cl == null) { + return ("Could not connect to Xiaomi cloud"); + } + boolean isStarting = homeListState == CloudListState.STARTING; + homeListState = CloudListState.REFRESHING; + for (String server : country.split(",")) { + try { + updateHomeList(server); + } catch (JsonParseException e) { + logger.debug("Parsing error getting home details: {}", e.getMessage()); + } + } + homeListState = CloudListState.AVAILABLE; + if (isStarting) { + printHomesandRooms(); + } return "done";// deviceList; }); + private void printHomesandRooms() { + for (Entry countryHome : homeLists.entrySet()) { + String server = countryHome.getKey(); + final HomeListDTO homelist = countryHome.getValue(); + for (HomeDTO home : homelist.getHomelist()) { + logger.debug("Server: {}, Home id: {}, Name {}", server, home.getId(), home.getName()); + for (HomeRoomDTO room : home.getRoomlist()) { + logger.debug("Server: {}, Home id: {}, Room id: {}, Name {}", server, home.getId(), room.getId(), + room.getName()); + } + } + } + } + @Activate public CloudConnector(@Reference HttpClientFactory httpClientFactory) { this.httpClient = httpClientFactory.createHttpClient(BINDING_ID); @@ -119,7 +168,7 @@ public boolean isConnected(boolean force) { if (c != null && c.booleanValue()) { return true; } - deviceListState = DeviceListState.FAILED; + deviceListState = CloudListState.FAILED; return false; } @@ -139,6 +188,21 @@ public String sendCloudCommand(String urlPart, String country, String parameters return cl.request(urlPart.startsWith("/") ? urlPart : "/" + urlPart, country, parameters); } + private void updateHomeList(String country) { + final @Nullable MiCloudConnector cl = this.cloudConnector; + if (isConnected() && cl != null) { + try { + JsonObject homelistInfo = cl.getHomeList(country.trim().toLowerCase()); + final HomeListDTO homelist = GSON.fromJson(homelistInfo, HomeListDTO.class); + if (homelist != null && homelist.getHomelist() != null && homelist.getHomelist().size() > 0) { + homeLists.put(country, homelist); + } + } catch (JsonSyntaxException e) { + logger.debug("Home List / Room info could not be updated for server '{}': {}", country, e.getMessage()); + } + } + } + public @Nullable RawType getMap(String mapId, String country) throws MiCloudException { logger.debug("Getting vacuum map {} from Xiaomi cloud server: '{}'", mapId, country); String mapCountry; @@ -203,11 +267,11 @@ private boolean logon() { if (connected) { getDevicesList(); } else { - deviceListState = DeviceListState.FAILED; + deviceListState = CloudListState.FAILED; } } catch (MiCloudException e) { connected = false; - deviceListState = DeviceListState.FAILED; + deviceListState = CloudListState.FAILED; logger.debug("Xiaomi cloud login failed: {}", e.getMessage()); } return connected; @@ -220,7 +284,7 @@ public List getDevicesList() { public @Nullable CloudDeviceDTO getDeviceInfo(String id) { getDevicesList(); - if (deviceListState != DeviceListState.AVAILABLE) { + if (deviceListState != CloudListState.AVAILABLE) { return null; } List devicedata = new ArrayList<>(); @@ -243,4 +307,61 @@ public List getDevicesList() { } return devicedata.get(0); } + + public HomeListDTO getHomeList(String server) { + refreshHomeList.getValue(); + return homeLists.getOrDefault(server, new HomeListDTO()); + } + + public ConcurrentHashMap getHomeLists() { + refreshHomeList.getValue(); + return homeLists; + } + + /** + * Get the room from the cloud given the room Id and country server + * + * @param room id + * @param country + * @return room + */ + + public @Nullable HomeRoomDTO getRoom(String id, String country) { + @Nullable + HomeListDTO homeList = homeLists.getOrDefault(country, new HomeListDTO()); + if (homeList.getHomelist() != null) { + for (HomeDTO home : homeList.getHomelist()) { + for (HomeRoomDTO room : home.getRoomlist()) { + if (room.getId().contentEquals(id)) { + return room; + } + } + } + } + return null; + } + + /** + * Get the room from the cloud given the room Id + * + * @param room id + * @return room + */ + public @Nullable HomeRoomDTO getRoom(String id) { + return getRoom(id, true); + } + + private @Nullable HomeRoomDTO getRoom(String id, boolean retry) { + for (Entry countryHome : homeLists.entrySet()) { + HomeRoomDTO room = getRoom(id, countryHome.getKey()); + if (room != null) { + return room; + } + } + if (retry) { + refreshHomeList.getValue(); + return getRoom(id, false); + } + return null; + } } diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeDTO.java new file mode 100644 index 0000000000000..9743890fd50ec --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeDTO.java @@ -0,0 +1,269 @@ +/** + * 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.miio.internal.cloud; + +import java.util.List; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This DTO class wraps the home json structure + * + * @author Marcel Verpaalen - Initial contribution + */ +public class HomeDTO { + + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + @SerializedName("bssid") + @Expose + private String bssid; + @SerializedName("dids") + @Expose + private List dids; + @SerializedName("temp_dids") + @Expose + private Object tempDids; + @SerializedName("icon") + @Expose + private String icon; + @SerializedName("shareflag") + @Expose + private Integer shareflag; + @SerializedName("permit_level") + @Expose + private Integer permitLevel; + @SerializedName("status") + @Expose + private Integer status; + @SerializedName("background") + @Expose + private String background; + @SerializedName("smart_room_background") + @Expose + private String smartRoomBackground; + @SerializedName("longitude") + @Expose + private Integer longitude; + @SerializedName("latitude") + @Expose + private Integer latitude; + @SerializedName("city_id") + @Expose + private Integer cityId; + @SerializedName("address") + @Expose + private String address; + @SerializedName("create_time") + @Expose + private Integer createTime; + @SerializedName("roomlist") + @Expose + private List roomlist; + @SerializedName("uid") + @Expose + private Integer uid; + @SerializedName("appear_home_list") + @Expose + private Object appearHomeList; + @SerializedName("popup_flag") + @Expose + private Integer popupFlag; + @SerializedName("popup_time_stamp") + @Expose + private Integer popupTimeStamp; + @SerializedName("car_did") + @Expose + private String carDid; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBssid() { + return bssid; + } + + public void setBssid(String bssid) { + this.bssid = bssid; + } + + public List getDids() { + return dids; + } + + public void setDids(List dids) { + this.dids = dids; + } + + public Object getTempDids() { + return tempDids; + } + + public void setTempDids(Object tempDids) { + this.tempDids = tempDids; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getShareflag() { + return shareflag; + } + + public void setShareflag(Integer shareflag) { + this.shareflag = shareflag; + } + + public Integer getPermitLevel() { + return permitLevel; + } + + public void setPermitLevel(Integer permitLevel) { + this.permitLevel = permitLevel; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getBackground() { + return background; + } + + public void setBackground(String background) { + this.background = background; + } + + public String getSmartRoomBackground() { + return smartRoomBackground; + } + + public void setSmartRoomBackground(String smartRoomBackground) { + this.smartRoomBackground = smartRoomBackground; + } + + public Integer getLongitude() { + return longitude; + } + + public void setLongitude(Integer longitude) { + this.longitude = longitude; + } + + public Integer getLatitude() { + return latitude; + } + + public void setLatitude(Integer latitude) { + this.latitude = latitude; + } + + public Integer getCityId() { + return cityId; + } + + public void setCityId(Integer cityId) { + this.cityId = cityId; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Integer getCreateTime() { + return createTime; + } + + public void setCreateTime(Integer createTime) { + this.createTime = createTime; + } + + public List getRoomlist() { + return roomlist; + } + + public void setRoomlist(List roomlist) { + this.roomlist = roomlist; + } + + public Integer getUid() { + return uid; + } + + public void setUid(Integer uid) { + this.uid = uid; + } + + public Object getAppearHomeList() { + return appearHomeList; + } + + public void setAppearHomeList(Object appearHomeList) { + this.appearHomeList = appearHomeList; + } + + public Integer getPopupFlag() { + return popupFlag; + } + + public void setPopupFlag(Integer popupFlag) { + this.popupFlag = popupFlag; + } + + public Integer getPopupTimeStamp() { + return popupTimeStamp; + } + + public void setPopupTimeStamp(Integer popupTimeStamp) { + this.popupTimeStamp = popupTimeStamp; + } + + public String getCarDid() { + return carDid; + } + + public void setCarDid(String carDid) { + this.carDid = carDid; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeListDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeListDTO.java new file mode 100644 index 0000000000000..01380b442fbf0 --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeListDTO.java @@ -0,0 +1,61 @@ +/** + * 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.miio.internal.cloud; + +import java.util.List; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This DTO class wraps the home json structure + * + * @author Marcel Verpaalen - Initial contribution + */ + +public class HomeListDTO { + + @SerializedName("homelist") + @Expose + private List homelist; + @SerializedName("has_more") + @Expose + private Boolean hasMore; + @SerializedName("max_id") + @Expose + private String maxId; + + public List getHomelist() { + return homelist; + } + + public void setHomelist(List homelist) { + this.homelist = homelist; + } + + public Boolean getHasMore() { + return hasMore; + } + + public void setHasMore(Boolean hasMore) { + this.hasMore = hasMore; + } + + public String getMaxId() { + return maxId; + } + + public void setMaxId(String maxId) { + this.maxId = maxId; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeRoomDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeRoomDTO.java new file mode 100644 index 0000000000000..85a5323e1a9be --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/HomeRoomDTO.java @@ -0,0 +1,126 @@ +/** + * 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.miio.internal.cloud; + +import java.util.List; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This DTO class wraps the home json structure + * + * @author Marcel Verpaalen - Initial contribution + */ +public class HomeRoomDTO { + + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + @SerializedName("bssid") + @Expose + private String bssid; + @SerializedName("parentid") + @Expose + private String parentid; + @SerializedName("dids") + @Expose + private List dids; + @SerializedName("icon") + @Expose + private String icon; + @SerializedName("background") + @Expose + private String background; + @SerializedName("shareflag") + @Expose + private Integer shareflag; + @SerializedName("create_time") + @Expose + private Integer createTime; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBssid() { + return bssid; + } + + public void setBssid(String bssid) { + this.bssid = bssid; + } + + public String getParentid() { + return parentid; + } + + public void setParentid(String parentid) { + this.parentid = parentid; + } + + public List getDids() { + return dids; + } + + public void setDids(List dids) { + this.dids = dids; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getBackground() { + return background; + } + + public void setBackground(String background) { + this.background = background; + } + + public Integer getShareflag() { + return shareflag; + } + + public void setShareflag(Integer shareflag) { + this.shareflag = shareflag; + } + + public Integer getCreateTime() { + return createTime; + } + + public void setCreateTime(Integer createTime) { + this.createTime = createTime; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java index d2700081dd296..47930d6861ae8 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/cloud/MiCloudConnector.java @@ -201,6 +201,25 @@ public String sendRPCCommand(String device, String country, String command) thro return request("/home/rpc/" + id, country, command); } + public JsonObject getHomeList(String country) { + String response = ""; + try { + response = request("/homeroom/gethome", country, + "{\"fg\":false,\"fetch_share\":true,\"fetch_share_dev\":true,\"limit\":300,\"app_ver\":7,\"fetch_cariot\":true}"); + logger.trace("gethome response: {}", response); + final JsonElement resp = JsonParser.parseString(response); + if (resp.isJsonObject() && resp.getAsJsonObject().has("result")) { + return resp.getAsJsonObject().get("result").getAsJsonObject(); + } + } catch (JsonParseException e) { + logger.info("{} error while parsing rooms: '{}'", e.getMessage(), response); + } catch (MiCloudException e) { + logger.info("{}", e.getMessage()); + loginFailedCounter++; + } + return new JsonObject(); + } + public List getDevices(String country) { final String response = getDeviceString(country); List devicesList = new ArrayList<>(); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index 3d6c4d3121345..1cb9d403fbc94 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -18,6 +18,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -41,9 +43,11 @@ import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService; import org.openhab.binding.miio.internal.cloud.CloudConnector; import org.openhab.binding.miio.internal.cloud.CloudUtil; +import org.openhab.binding.miio.internal.cloud.HomeRoomDTO; import org.openhab.binding.miio.internal.cloud.MiCloudException; import org.openhab.binding.miio.internal.robot.ConsumablesType; import org.openhab.binding.miio.internal.robot.FanModeType; +import org.openhab.binding.miio.internal.robot.HistoryRecordDTO; import org.openhab.binding.miio.internal.robot.RRMapDraw; import org.openhab.binding.miio.internal.robot.RRMapDrawOptions; import org.openhab.binding.miio.internal.robot.RobotCababilities; @@ -80,6 +84,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; /** @@ -445,31 +450,113 @@ private boolean updateHistory(JsonObject historyData) { return true; } - private void updateHistoryRecord(JsonArray historyData) { - ZonedDateTime startTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(historyData.get(0).getAsLong()), - ZoneId.systemDefault()); - ZonedDateTime endTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(historyData.get(1).getAsLong()), - ZoneId.systemDefault()); - long duration = TimeUnit.SECONDS.toMinutes(historyData.get(2).getAsLong()); - double area = historyData.get(3).getAsDouble() / 1000000D; - int error = historyData.get(4).getAsInt(); - int finished = historyData.get(5).getAsInt(); + private void updateHistoryRecordLegacy(JsonArray historyData) { + HistoryRecordDTO historyRecord = new HistoryRecordDTO(); + for (int i = 0; i < historyData.size(); ++i) { + try { + BigInteger value = historyData.get(i).getAsBigInteger(); + switch (i) { + case 0: + historyRecord.setStart(ZonedDateTime + .ofInstant(Instant.ofEpochSecond(value.longValue()), ZoneId.systemDefault()) + .toString()); + break; + case 1: + historyRecord.setStart(ZonedDateTime + .ofInstant(Instant.ofEpochSecond(value.longValue()), ZoneId.systemDefault()) + .toString()); + break; + case 2: + historyRecord.setDuration(value.intValue()); + break; + case 3: + historyRecord.setArea(new BigDecimal(value).divide(BigDecimal.valueOf(1000000))); + break; + case 4: + historyRecord.setError(value.intValue()); + break; + case 5: + historyRecord.setFinished(value.intValue()); + break; + case 6: + historyRecord.setStartType(value.intValue()); + break; + case 7: + historyRecord.setCleanType(value.intValue()); + break; + case 8: + historyRecord.setFinishReason(value.intValue()); + break; + } + } catch (ClassCastException | NumberFormatException | IllegalStateException e) { + } + } + updateHistoryRecord(historyRecord); + } + + private void updateHistoryRecord(HistoryRecordDTO historyRecordDTO) { JsonObject historyRecord = new JsonObject(); - historyRecord.addProperty("start", startTime.toString()); - historyRecord.addProperty("end", endTime.toString()); - historyRecord.addProperty("duration", duration); - historyRecord.addProperty("area", area); - historyRecord.addProperty("error", error); - historyRecord.addProperty("finished", finished); - updateState(CHANNEL_HISTORY_START_TIME, new DateTimeType(startTime)); - updateState(CHANNEL_HISTORY_END_TIME, new DateTimeType(endTime)); - updateState(CHANNEL_HISTORY_DURATION, new QuantityType<>(duration, Units.MINUTE)); - updateState(CHANNEL_HISTORY_AREA, new QuantityType<>(area, SIUnits.SQUARE_METRE)); - updateState(CHANNEL_HISTORY_ERROR, new DecimalType(error)); - updateState(CHANNEL_HISTORY_FINISH, new DecimalType(finished)); + if (historyRecordDTO.getStart() != null) { + historyRecord.addProperty("start", historyRecordDTO.getStart().split("\\+")[0]); + updateState(CHANNEL_HISTORY_START_TIME, new DateTimeType(historyRecordDTO.getStart().split("\\+")[0])); + } + if (historyRecordDTO.getEnd() != null) { + historyRecord.addProperty("end", historyRecordDTO.getEnd().split("\\+")[0]); + updateState(CHANNEL_HISTORY_END_TIME, new DateTimeType(historyRecordDTO.getEnd().split("\\+")[0])); + } + if (historyRecordDTO.getDuration() != null) { + long duration = TimeUnit.SECONDS.toMinutes(historyRecordDTO.getDuration().longValue()); + historyRecord.addProperty("duration", duration); + updateState(CHANNEL_HISTORY_DURATION, new QuantityType<>(duration, Units.MINUTE)); + } + if (historyRecordDTO.getArea() != null) { + historyRecord.addProperty("area", historyRecordDTO.getArea()); + updateState(CHANNEL_HISTORY_AREA, new QuantityType<>(historyRecordDTO.getArea(), SIUnits.SQUARE_METRE)); + } + if (historyRecordDTO.getError() != null) { + historyRecord.addProperty("error", historyRecordDTO.getError()); + updateState(CHANNEL_HISTORY_ERROR, new DecimalType(historyRecordDTO.getError())); + } + if (historyRecordDTO.getFinished() != null) { + historyRecord.addProperty("finished", historyRecordDTO.getFinished()); + updateState(CHANNEL_HISTORY_FINISH, new DecimalType(historyRecordDTO.getFinished())); + } + if (historyRecordDTO.getFinishReason() != null) { + historyRecord.addProperty("finish_reason", historyRecordDTO.getFinishReason()); + updateState(CHANNEL_HISTORY_FINISHREASON, new DecimalType(historyRecordDTO.getFinishReason())); + } + if (historyRecordDTO.getDustCollectionStatus() != null) { + historyRecord.addProperty("dust_collection_status", historyRecordDTO.getDustCollectionStatus()); + updateState(CHANNEL_HISTORY_DUSTCOLLECTION, new DecimalType(historyRecordDTO.getDustCollectionStatus())); + } updateState(CHANNEL_HISTORY_RECORD, new StringType(historyRecord.toString())); } + private void updateRoomMapping(MiIoSendCommand response) { + for (RobotCababilities cmd : FEATURES_CHANNELS) { + if (response.getCommand().getCommand().contentEquals(cmd.getCommand())) { + if (response.getResult().isJsonArray()) { + JsonArray rooms = response.getResult().getAsJsonArray(); + JsonArray mappedRoom = new JsonArray(); + for (JsonElement roomE : rooms) { + JsonArray room = roomE.getAsJsonArray(); + HomeRoomDTO name = cloudConnector.getRoom(room.get(1).getAsString()); + if (name != null && name.getName() != null) { + room.add(name.getName()); + } else { + room.add("not found"); + } + mappedRoom.add(room); + } + updateState(cmd.getChannel(), new StringType(mappedRoom.toString())); + } else { + updateState(cmd.getChannel(), new StringType(response.getResult().toString())); + } + break; + } + } + } + @Override protected boolean skipUpdate() { if (!hasConnection()) { @@ -525,6 +612,7 @@ public void initialize() { this.mapDrawOptions = RRMapDrawOptions .getOptionsFromFile(BINDING_USERDATA_PATH + File.separator + "mapConfig.json", logger); updateState(RobotCababilities.SEGMENT_CLEAN.getChannel(), new StringType("-")); + cloudConnector.getHomeLists(); } @Override @@ -570,7 +658,13 @@ public void onMessageReceived(MiIoSendCommand response) { case CLEAN_RECORD_GET: if (response.getResult().isJsonArray() && response.getResult().getAsJsonArray().size() > 0 && response.getResult().getAsJsonArray().get(0).isJsonArray()) { - updateHistoryRecord(response.getResult().getAsJsonArray().get(0).getAsJsonArray()); + updateHistoryRecordLegacy(response.getResult().getAsJsonArray().get(0).getAsJsonArray()); + } else if (response.getResult().isJsonObject()) { + final HistoryRecordDTO historyRecordDTO = GSON.fromJson(response.getResult().getAsJsonObject(), + HistoryRecordDTO.class); + if (historyRecordDTO != null) { + updateHistoryRecord(historyRecordDTO); + } } else { logger.debug("Could not extract cleaning history record from: {}", response); } @@ -589,11 +683,13 @@ public void onMessageReceived(MiIoSendCommand response) { case GET_LED_STATUS: updateNumericChannel(response); break; + case GET_ROOM_MAPPING: + updateRoomMapping(response); + break; case GET_CARPET_MODE: case GET_FW_FEATURES: case GET_CUSTOMIZED_CLEAN_MODE: case GET_MULTI_MAP_LIST: - case GET_ROOM_MAPPING: for (RobotCababilities cmd : FEATURES_CHANNELS) { if (response.getCommand().getCommand().contentEquals(cmd.getCommand())) { updateState(cmd.getChannel(), new StringType(response.getResult().toString())); diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/HistoryRecordDTO.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/HistoryRecordDTO.java new file mode 100644 index 0000000000000..c30b044536b68 --- /dev/null +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/robot/HistoryRecordDTO.java @@ -0,0 +1,148 @@ +/** + * 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.miio.internal.robot; + +import java.math.BigDecimal; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This DTO class wraps the history record message json structure + * + * @author Marcel Verpaalen - Initial contribution + */ +public class HistoryRecordDTO { + + @SerializedName("start") + @Expose + private String start; + @SerializedName("end") + @Expose + private String end; + @SerializedName("duration") + @Expose + private Integer duration; + @SerializedName("area") + @Expose + private BigDecimal area; + @SerializedName("clean_time") + @Expose + private Integer cleanTime; + @SerializedName("error") + @Expose + private Integer error; + @SerializedName("finished") + @Expose + private Integer finished; + @SerializedName("start_type") + @Expose + private Integer startType; + @SerializedName("clean_type") + @Expose + private Integer cleanType; + @SerializedName("finish_reason") + @Expose + private Integer finishReason; + @SerializedName("dust_collection_status") + @Expose + private Integer dustCollectionStatus; + + public final String getStart() { + return start; + } + + public final void setStart(String start) { + this.start = start; + } + + public final String getEnd() { + return end; + } + + public final void setEnd(String end) { + this.end = end; + } + + public final Integer getDuration() { + return duration; + } + + public final void setDuration(Integer duration) { + this.duration = duration; + } + + public final BigDecimal getArea() { + return area; + } + + public final void setArea(BigDecimal area) { + this.area = area; + } + + public final Integer getCleanTime() { + return cleanTime; + } + + public final void setCleanTime(Integer cleanTime) { + this.cleanTime = cleanTime; + } + + public final Integer getError() { + return error; + } + + public final void setError(Integer error) { + this.error = error; + } + + public final Integer getFinished() { + return finished; + } + + public final void setFinished(Integer finished) { + this.finished = finished; + } + + public final Integer getStartType() { + return startType; + } + + public final void setStartType(Integer startType) { + this.startType = startType; + } + + public final Integer getCleanType() { + return cleanType; + } + + public final void setCleanType(Integer cleanType) { + this.cleanType = cleanType; + } + + public final Integer getFinishReason() { + return finishReason; + } + + public final void setFinishReason(Integer finishReason) { + this.finishReason = finishReason; + } + + public final Integer getDustCollectionStatus() { + return dustCollectionStatus; + } + + public final void setDustCollectionStatus(Integer dustCollectionStatus) { + this.dustCollectionStatus = dustCollectionStatus; + } +} diff --git a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml index ee8887766b6e7..9bdfb67ae1056 100644 --- a/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml +++ b/bundles/org.openhab.binding.miio/src/main/resources/OH-INF/thing/vacuumThing.xml @@ -87,6 +87,8 @@ + + @@ -377,6 +379,16 @@ + + Number + + + + + Number + + + String