2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.miio.internal.handler;
15 import static org.openhab.binding.miio.internal.MiIoBindingConstants.*;
17 import java.io.ByteArrayInputStream;
18 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.text.SimpleDateFormat;
22 import java.time.Instant;
23 import java.time.ZoneId;
24 import java.time.ZonedDateTime;
25 import java.util.Date;
26 import java.util.Map.Entry;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.TimeUnit;
30 import javax.imageio.ImageIO;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
35 import org.openhab.binding.miio.internal.MiIoCommand;
36 import org.openhab.binding.miio.internal.MiIoSendCommand;
37 import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
38 import org.openhab.binding.miio.internal.cloud.CloudConnector;
39 import org.openhab.binding.miio.internal.cloud.CloudUtil;
40 import org.openhab.binding.miio.internal.cloud.MiCloudException;
41 import org.openhab.binding.miio.internal.robot.ConsumablesType;
42 import org.openhab.binding.miio.internal.robot.FanModeType;
43 import org.openhab.binding.miio.internal.robot.RRMapDraw;
44 import org.openhab.binding.miio.internal.robot.RobotCababilities;
45 import org.openhab.binding.miio.internal.robot.StatusDTO;
46 import org.openhab.binding.miio.internal.robot.StatusType;
47 import org.openhab.binding.miio.internal.robot.VacuumErrorType;
48 import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication;
49 import org.openhab.core.cache.ExpiringCache;
50 import org.openhab.core.library.types.DateTimeType;
51 import org.openhab.core.library.types.DecimalType;
52 import org.openhab.core.library.types.OnOffType;
53 import org.openhab.core.library.types.QuantityType;
54 import org.openhab.core.library.types.RawType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.library.unit.SIUnits;
57 import org.openhab.core.library.unit.SmartHomeUnits;
58 import org.openhab.core.thing.Channel;
59 import org.openhab.core.thing.ChannelUID;
60 import org.openhab.core.thing.Thing;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.binding.builder.ChannelBuilder;
63 import org.openhab.core.thing.binding.builder.ThingBuilder;
64 import org.openhab.core.thing.type.ChannelType;
65 import org.openhab.core.thing.type.ChannelTypeRegistry;
66 import org.openhab.core.types.Command;
67 import org.openhab.core.types.RefreshType;
68 import org.openhab.core.types.State;
69 import org.openhab.core.types.UnDefType;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
73 import com.google.gson.Gson;
74 import com.google.gson.GsonBuilder;
75 import com.google.gson.JsonArray;
76 import com.google.gson.JsonObject;
79 * The {@link MiIoVacuumHandler} is responsible for handling commands, which are
80 * sent to one of the channels.
82 * @author Marcel Verpaalen - Initial contribution
85 public class MiIoVacuumHandler extends MiIoAbstractHandler {
86 private final Logger logger = LoggerFactory.getLogger(MiIoVacuumHandler.class);
87 private static final float MAP_SCALE = 2.0f;
88 private static final SimpleDateFormat DATEFORMATTER = new SimpleDateFormat("yyyyMMdd-HHmmss");
89 private static final Gson GSON = new GsonBuilder().serializeNulls().create();
90 private final ChannelUID mapChannelUid;
92 private ExpiringCache<String> status;
93 private ExpiringCache<String> consumables;
94 private ExpiringCache<String> dnd;
95 private ExpiringCache<String> history;
97 private ExpiringCache<String> map;
98 private String lastHistoryId = "";
99 private String lastMap = "";
100 private CloudConnector cloudConnector;
101 private boolean hasChannelStructure;
102 private ConcurrentHashMap<RobotCababilities, Boolean> deviceCapabilities = new ConcurrentHashMap<>();
103 private ChannelTypeRegistry channelTypeRegistry;
105 public MiIoVacuumHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
106 CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry) {
107 super(thing, miIoDatabaseWatchService);
108 this.cloudConnector = cloudConnector;
109 this.channelTypeRegistry = channelTypeRegistry;
110 mapChannelUid = new ChannelUID(thing.getUID(), CHANNEL_VACUUM_MAP);
111 status = new ExpiringCache<>(CACHE_EXPIRY, () -> {
113 int ret = sendCommand(MiIoCommand.GET_STATUS);
117 } catch (Exception e) {
118 logger.debug("Error during status refresh: {}", e.getMessage(), e);
122 consumables = new ExpiringCache<>(CACHE_EXPIRY, () -> {
124 int ret = sendCommand(MiIoCommand.CONSUMABLES_GET);
128 } catch (Exception e) {
129 logger.debug("Error during consumables refresh: {}", e.getMessage(), e);
133 dnd = new ExpiringCache<>(CACHE_EXPIRY, () -> {
135 int ret = sendCommand(MiIoCommand.DND_GET);
139 } catch (Exception e) {
140 logger.debug("Error during dnd refresh: {}", e.getMessage(), e);
144 history = new ExpiringCache<>(CACHE_EXPIRY, () -> {
146 int ret = sendCommand(MiIoCommand.CLEAN_SUMMARY_GET);
150 } catch (Exception e) {
151 logger.debug("Error during cleaning data refresh: {}", e.getMessage(), e);
155 map = new ExpiringCache<String>(CACHE_EXPIRY, () -> {
157 int ret = sendCommand(MiIoCommand.GET_MAP);
161 } catch (Exception e) {
162 logger.debug("Error during dnd refresh: {}", e.getMessage(), e);
169 public void handleCommand(ChannelUID channelUID, Command command) {
170 if (getConnection() == null) {
171 logger.debug("Vacuum {} not online. Command {} ignored", getThing().getUID(), command.toString());
174 if (command == RefreshType.REFRESH) {
175 logger.debug("Refreshing {}", channelUID);
178 if (channelUID.getId().equals(CHANNEL_VACUUM_MAP)) {
179 sendCommand(MiIoCommand.GET_MAP);
183 if (channelUID.getId().equals(CHANNEL_VACUUM)) {
184 if (command instanceof OnOffType) {
185 if (command.equals(OnOffType.ON)) {
186 sendCommand(MiIoCommand.START_VACUUM);
190 sendCommand(MiIoCommand.STOP_VACUUM);
191 scheduler.schedule(() -> {
192 sendCommand(MiIoCommand.CHARGE);
194 }, 2000, TimeUnit.MILLISECONDS);
199 if (channelUID.getId().equals(CHANNEL_CONTROL)) {
200 if (command.toString().equals("vacuum")) {
201 sendCommand(MiIoCommand.START_VACUUM);
202 } else if (command.toString().equals("spot")) {
203 sendCommand(MiIoCommand.START_SPOT);
204 } else if (command.toString().equals("pause")) {
205 sendCommand(MiIoCommand.PAUSE);
206 } else if (command.toString().equals("dock")) {
207 sendCommand(MiIoCommand.STOP_VACUUM);
208 scheduler.schedule(() -> {
209 sendCommand(MiIoCommand.CHARGE);
211 }, 2000, TimeUnit.MILLISECONDS);
214 logger.info("Command {} not recognised", command.toString());
219 if (channelUID.getId().equals(CHANNEL_FAN_POWER)) {
220 sendCommand(MiIoCommand.SET_MODE, "[" + command.toString() + "]");
224 if (channelUID.getId().equals(RobotCababilities.WATERBOX_MODE.getChannel())) {
225 sendCommand(MiIoCommand.SET_WATERBOX_MODE, "[" + command.toString() + "]");
229 if (channelUID.getId().equals(RobotCababilities.SEGMENT_CLEAN.getChannel()) && !command.toString().isEmpty()) {
230 sendCommand(MiIoCommand.START_SEGMENT, "[" + command.toString() + "]");
231 updateState(RobotCababilities.SEGMENT_CLEAN.getChannel(), UnDefType.UNDEF);
235 if (channelUID.getId().equals(CHANNEL_FAN_CONTROL)) {
236 if (Integer.valueOf(command.toString()) > 0) {
237 sendCommand(MiIoCommand.SET_MODE, "[" + command.toString() + "]");
242 if (channelUID.getId().equals(CHANNEL_CONSUMABLE_RESET)) {
243 sendCommand(MiIoCommand.CONSUMABLES_RESET, "[" + command.toString() + "]");
244 updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none"));
246 if (channelUID.getId().equals(CHANNEL_COMMAND)) {
247 cmds.put(sendCommand(command.toString()), command.toString());
251 private void forceStatusUpdate() {
252 status.invalidateValue();
256 private void safeUpdateState(String channelID, @Nullable Integer state) {
258 updateState(channelID, new DecimalType(state));
260 logger.debug("Channel {} not update. value not available.", channelID);
264 private boolean updateVacuumStatus(JsonObject statusData) {
265 StatusDTO statusInfo = GSON.fromJson(statusData, StatusDTO.class);
266 safeUpdateState(CHANNEL_BATTERY, statusInfo.getBattery());
267 if (statusInfo.getCleanArea() != null) {
268 updateState(CHANNEL_CLEAN_AREA,
269 new QuantityType<>(statusInfo.getCleanArea() / 1000000.0, SIUnits.SQUARE_METRE));
271 if (statusInfo.getCleanTime() != null) {
272 updateState(CHANNEL_CLEAN_TIME,
273 new QuantityType<>(TimeUnit.SECONDS.toMinutes(statusInfo.getCleanTime()), SmartHomeUnits.MINUTE));
275 safeUpdateState(CHANNEL_DND_ENABLED, statusInfo.getDndEnabled());
277 if (statusInfo.getErrorCode() != null) {
278 updateState(CHANNEL_ERROR_CODE,
279 new StringType(VacuumErrorType.getType(statusInfo.getErrorCode()).getDescription()));
280 safeUpdateState(CHANNEL_ERROR_ID, statusInfo.getErrorCode());
283 if (statusInfo.getFanPower() != null) {
284 updateState(CHANNEL_FAN_POWER, new DecimalType(statusInfo.getFanPower()));
285 updateState(CHANNEL_FAN_CONTROL, new DecimalType(FanModeType.getType(statusInfo.getFanPower()).getId()));
287 safeUpdateState(CHANNEL_IN_CLEANING, statusInfo.getInCleaning());
288 safeUpdateState(CHANNEL_MAP_PRESENT, statusInfo.getMapPresent());
289 if (statusInfo.getState() != null) {
290 StatusType state = StatusType.getType(statusInfo.getState());
291 updateState(CHANNEL_STATE, new StringType(state.getDescription()));
292 updateState(CHANNEL_STATE_ID, new DecimalType(statusInfo.getState()));
294 State vacuum = OnOffType.OFF;
302 vacuum = OnOffType.ON;
317 vacuum = OnOffType.ON;
323 if (control.equals("undef")) {
324 updateState(CHANNEL_CONTROL, UnDefType.UNDEF);
326 updateState(CHANNEL_CONTROL, new StringType(control));
328 updateState(CHANNEL_VACUUM, vacuum);
330 if (deviceCapabilities.containsKey(RobotCababilities.WATERBOX_MODE)) {
331 safeUpdateState(RobotCababilities.WATERBOX_MODE.getChannel(), statusInfo.getWaterBoxMode());
333 if (deviceCapabilities.containsKey(RobotCababilities.WATERBOX_STATUS)) {
334 safeUpdateState(RobotCababilities.WATERBOX_STATUS.getChannel(), statusInfo.getWaterBoxStatus());
336 if (deviceCapabilities.containsKey(RobotCababilities.WATERBOX_CARRIAGE)) {
337 safeUpdateState(RobotCababilities.WATERBOX_CARRIAGE.getChannel(), statusInfo.getWaterBoxCarriageStatus());
339 if (deviceCapabilities.containsKey(RobotCababilities.LOCKSTATUS)) {
340 safeUpdateState(RobotCababilities.LOCKSTATUS.getChannel(), statusInfo.getLockStatus());
342 if (deviceCapabilities.containsKey(RobotCababilities.MOP_FORBIDDEN)) {
343 safeUpdateState(RobotCababilities.MOP_FORBIDDEN.getChannel(), statusInfo.getMopForbiddenEnable());
348 private boolean updateConsumables(JsonObject consumablesData) {
349 int mainBrush = consumablesData.get("main_brush_work_time").getAsInt();
350 int sideBrush = consumablesData.get("side_brush_work_time").getAsInt();
351 int filter = consumablesData.get("filter_work_time").getAsInt();
352 int sensor = consumablesData.get("sensor_dirty_time").getAsInt();
353 updateState(CHANNEL_CONSUMABLE_MAIN_TIME, new QuantityType<>(
354 ConsumablesType.remainingHours(mainBrush, ConsumablesType.MAIN_BRUSH), SmartHomeUnits.HOUR));
355 updateState(CHANNEL_CONSUMABLE_MAIN_PERC,
356 new DecimalType(ConsumablesType.remainingPercent(mainBrush, ConsumablesType.MAIN_BRUSH)));
357 updateState(CHANNEL_CONSUMABLE_SIDE_TIME, new QuantityType<>(
358 ConsumablesType.remainingHours(sideBrush, ConsumablesType.SIDE_BRUSH), SmartHomeUnits.HOUR));
359 updateState(CHANNEL_CONSUMABLE_SIDE_PERC,
360 new DecimalType(ConsumablesType.remainingPercent(sideBrush, ConsumablesType.SIDE_BRUSH)));
361 updateState(CHANNEL_CONSUMABLE_FILTER_TIME, new QuantityType<>(
362 ConsumablesType.remainingHours(filter, ConsumablesType.FILTER), SmartHomeUnits.HOUR));
363 updateState(CHANNEL_CONSUMABLE_FILTER_PERC,
364 new DecimalType(ConsumablesType.remainingPercent(filter, ConsumablesType.FILTER)));
365 updateState(CHANNEL_CONSUMABLE_SENSOR_TIME, new QuantityType<>(
366 ConsumablesType.remainingHours(sensor, ConsumablesType.SENSOR), SmartHomeUnits.HOUR));
367 updateState(CHANNEL_CONSUMABLE_SENSOR_PERC,
368 new DecimalType(ConsumablesType.remainingPercent(sensor, ConsumablesType.SENSOR)));
372 private boolean updateDnD(JsonObject dndData) {
373 logger.trace("Do not disturb data: {}", dndData.toString());
374 updateState(CHANNEL_DND_FUNCTION, new DecimalType(dndData.get("enabled").getAsBigDecimal()));
375 updateState(CHANNEL_DND_START, new StringType(String.format("%02d:%02d", dndData.get("start_hour").getAsInt(),
376 dndData.get("start_minute").getAsInt())));
377 updateState(CHANNEL_DND_END, new StringType(
378 String.format("%02d:%02d", dndData.get("end_hour").getAsInt(), dndData.get("end_minute").getAsInt())));
382 private boolean updateHistory(JsonArray historyData) {
383 logger.trace("Cleaning history data: {}", historyData.toString());
384 updateState(CHANNEL_HISTORY_TOTALTIME,
385 new QuantityType<>(TimeUnit.SECONDS.toMinutes(historyData.get(0).getAsLong()), SmartHomeUnits.MINUTE));
386 updateState(CHANNEL_HISTORY_TOTALAREA,
387 new QuantityType<>(historyData.get(1).getAsDouble() / 1000000D, SIUnits.SQUARE_METRE));
388 updateState(CHANNEL_HISTORY_COUNT, new DecimalType(historyData.get(2).toString()));
389 if (historyData.get(3).getAsJsonArray().size() > 0) {
390 String lastClean = historyData.get(3).getAsJsonArray().get(0).getAsString();
391 if (!lastClean.equals(lastHistoryId)) {
392 lastHistoryId = lastClean;
393 sendCommand(MiIoCommand.CLEAN_RECORD_GET, "[" + lastClean + "]");
399 private void updateHistoryRecord(JsonArray historyData) {
400 ZonedDateTime startTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(historyData.get(0).getAsLong()),
401 ZoneId.systemDefault());
402 ZonedDateTime endTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(historyData.get(1).getAsLong()),
403 ZoneId.systemDefault());
404 long duration = TimeUnit.SECONDS.toMinutes(historyData.get(2).getAsLong());
405 double area = historyData.get(3).getAsDouble() / 1000000D;
406 int error = historyData.get(4).getAsInt();
407 int finished = historyData.get(5).getAsInt();
408 JsonObject historyRecord = new JsonObject();
409 historyRecord.addProperty("start", startTime.toString());
410 historyRecord.addProperty("end", endTime.toString());
411 historyRecord.addProperty("duration", duration);
412 historyRecord.addProperty("area", area);
413 historyRecord.addProperty("error", error);
414 historyRecord.addProperty("finished", finished);
415 updateState(CHANNEL_HISTORY_START_TIME, new DateTimeType(startTime));
416 updateState(CHANNEL_HISTORY_END_TIME, new DateTimeType(endTime));
417 updateState(CHANNEL_HISTORY_DURATION, new QuantityType<>(duration, SmartHomeUnits.MINUTE));
418 updateState(CHANNEL_HISTORY_AREA, new QuantityType<>(area, SIUnits.SQUARE_METRE));
419 updateState(CHANNEL_HISTORY_ERROR, new DecimalType(error));
420 updateState(CHANNEL_HISTORY_FINISH, new DecimalType(finished));
421 updateState(CHANNEL_HISTORY_RECORD, new StringType(historyRecord.toString()));
425 protected boolean skipUpdate() {
426 if (!hasConnection()) {
427 logger.debug("Skipping periodic update for '{}'. No Connection", getThing().getUID().toString());
430 if (ThingStatusDetail.CONFIGURATION_ERROR.equals(getThing().getStatusInfo().getStatusDetail())) {
431 logger.debug("Skipping periodic update for '{}' UID '{}'. Thing Status", getThing().getUID().toString(),
432 getThing().getStatusInfo().getStatusDetail());
436 final MiIoAsyncCommunication mc = miioCom;
437 if (mc != null && mc.getQueueLength() > MAX_QUEUE) {
438 logger.debug("Skipping periodic update for '{}'. {} elements in queue.", getThing().getUID().toString(),
439 mc.getQueueLength());
446 protected synchronized void updateData() {
447 if (!hasConnection() || skipUpdate()) {
450 logger.debug("Periodic update for '{}' ({})", getThing().getUID().toString(), getThing().getThingTypeUID());
456 consumables.getValue();
457 if (lastMap.isEmpty() || stateId != 8) {
458 if (isLinked(mapChannelUid)) {
462 } catch (Exception e) {
463 logger.debug("Error while updating '{}': '{}", getThing().getUID().toString(), e.getLocalizedMessage());
468 public void initialize() {
470 hasChannelStructure = false;
474 protected boolean initializeData() {
475 updateState(CHANNEL_CONSUMABLE_RESET, new StringType("none"));
476 return super.initializeData();
480 public void onMessageReceived(MiIoSendCommand response) {
481 super.onMessageReceived(response);
482 if (response.isError()) {
485 switch (response.getCommand()) {
487 if (response.getResult().isJsonArray()) {
488 JsonObject statusResponse = response.getResult().getAsJsonArray().get(0).getAsJsonObject();
489 if (!hasChannelStructure) {
490 setCapabilities(statusResponse);
491 createCapabilityChannels();
493 updateVacuumStatus(statusResponse);
496 case CONSUMABLES_GET:
497 if (response.getResult().isJsonArray()) {
498 updateConsumables(response.getResult().getAsJsonArray().get(0).getAsJsonObject());
502 if (response.getResult().isJsonArray()) {
503 updateDnD(response.getResult().getAsJsonArray().get(0).getAsJsonObject());
506 case CLEAN_SUMMARY_GET:
507 if (response.getResult().isJsonArray()) {
508 updateHistory(response.getResult().getAsJsonArray());
511 case CLEAN_RECORD_GET:
512 if (response.getResult().isJsonArray() && response.getResult().getAsJsonArray().size() > 0
513 && response.getResult().getAsJsonArray().get(0).isJsonArray()) {
514 updateHistoryRecord(response.getResult().getAsJsonArray().get(0).getAsJsonArray());
516 logger.debug("Could not extract cleaning history record from: {}", response);
520 if (response.getResult().isJsonArray()) {
521 String mapresponse = response.getResult().getAsJsonArray().get(0).getAsString();
522 if (!mapresponse.contentEquals("retry") && !mapresponse.contentEquals(lastMap)) {
523 lastMap = mapresponse;
524 scheduler.submit(() -> updateState(CHANNEL_VACUUM_MAP, getMap(mapresponse)));
529 updateState(CHANNEL_COMMAND, new StringType(response.getResponse().toString()));
536 private void setCapabilities(JsonObject statusResponse) {
537 for (RobotCababilities capability : RobotCababilities.values()) {
538 if (statusResponse.has(capability.getStatusFieldName())) {
539 deviceCapabilities.putIfAbsent(capability, false);
540 logger.debug("Setting additional vacuum {}", capability);
545 private void createCapabilityChannels() {
546 ThingBuilder thingBuilder = editThing();
549 for (Entry<RobotCababilities, Boolean> robotCapability : deviceCapabilities.entrySet()) {
550 RobotCababilities capability = robotCapability.getKey();
551 Boolean channelCreated = robotCapability.getValue();
552 if (!channelCreated) {
553 if (thing.getChannels().stream()
554 .anyMatch(ch -> ch.getUID().getId().equalsIgnoreCase(capability.getChannel()))) {
555 logger.debug("Channel already available...skip creation of channel '{}'.", capability.getChannel());
556 deviceCapabilities.replace(capability, true);
559 logger.debug("Creating dynamic channel for capability {}", capability);
560 ChannelType channelType = channelTypeRegistry.getChannelType(capability.getChannelType());
561 if (channelType != null) {
562 logger.debug("Found channelType '{}' for capability {}", channelType, capability.name());
563 ChannelUID channelUID = new ChannelUID(getThing().getUID(), capability.getChannel());
564 Channel channel = ChannelBuilder.create(channelUID, channelType.getItemType())
565 .withType(capability.getChannelType()).withLabel(channelType.getLabel()).build();
566 thingBuilder.withChannel(channel);
569 logger.debug("ChannelType {} not found (Unexpected). Available types:",
570 capability.getChannelType());
571 for (ChannelType ct : channelTypeRegistry.getChannelTypes()) {
572 logger.debug("Available channelType: '{}' '{}' '{}'", ct.getUID(), ct.toString(),
573 ct.getConfigDescriptionURI());
579 updateThing(thingBuilder.build());
581 hasChannelStructure = true;
584 private State getMap(String map) {
585 final MiIoBindingConfiguration configuration = this.configuration;
586 if (configuration != null && cloudConnector.isConnected()) {
588 final @Nullable RawType mapDl = cloudConnector.getMap(map,
589 (configuration.cloudServer != null) ? configuration.cloudServer : "");
591 byte[] mapData = mapDl.getBytes();
592 RRMapDraw rrMap = RRMapDraw.loadImage(new ByteArrayInputStream(mapData));
593 ByteArrayOutputStream baos = new ByteArrayOutputStream();
594 if (logger.isDebugEnabled()) {
595 final String mapPath = BINDING_USERDATA_PATH + File.separator + map
596 + DATEFORMATTER.format(new Date()) + ".rrmap";
597 CloudUtil.writeBytesToFileNio(mapData, mapPath);
598 logger.debug("Mapdata saved to {}", mapPath);
600 ImageIO.write(rrMap.getImage(MAP_SCALE), "jpg", baos);
601 byte[] byteArray = baos.toByteArray();
602 if (byteArray != null && byteArray.length > 0) {
603 return new RawType(byteArray, "image/jpeg");
605 logger.debug("Mapdata empty removing image");
606 return UnDefType.UNDEF;
609 } catch (MiCloudException e) {
610 logger.debug("Error getting data from Xiaomi cloud. Mapdata could not be updated: {}", e.getMessage());
611 } catch (IOException e) {
612 logger.debug("Mapdata could not be updated: {}", e.getMessage());
615 logger.debug("Not connected to Xiaomi cloud. Cannot retreive new map: {}", map);
617 return UnDefType.UNDEF;