Skip to content

Commit

Permalink
[miio] Fix parsing error clear record (openhab#17350)
Browse files Browse the repository at this point in the history
* [miio] Fix parsing error clear record

Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
Signed-off-by: Patrik Gfeller <patrik.gfeller@proton.me>
  • Loading branch information
marcelrv authored and pgfeller committed Sep 29, 2024
1 parent efa0434 commit 0d2857d
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
public class MiIoVacuumHandler extends MiIoAbstractHandler {
private final Logger logger = LoggerFactory.getLogger(MiIoVacuumHandler.class);
private static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
private static final DateTimeFormatter PARSER_TZ = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
private final ChannelUID mapChannelUid;

Expand Down Expand Up @@ -504,19 +505,20 @@ private boolean updateHistory(JsonObject historyData) {

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());
.format(PARSER_TZ));
break;
case 1:
historyRecord.setEnd(ZonedDateTime
.ofInstant(Instant.ofEpochSecond(value.longValue()), ZoneId.systemDefault())
.toString());
.format(PARSER_TZ));
break;
case 2:
historyRecord.setDuration(value.intValue());
Expand Down Expand Up @@ -549,14 +551,14 @@ private void updateHistoryRecordLegacy(JsonArray historyData) {
private void updateHistoryRecord(HistoryRecordDTO historyRecordDTO) {
JsonObject historyRecord = GSON.toJsonTree(historyRecordDTO).getAsJsonObject();
if (historyRecordDTO.getStart() != null) {
historyRecord.addProperty("start", historyRecordDTO.getStart().split("\\+")[0].split("\\-")[0]);
updateState(CHANNEL_HISTORY_START_TIME,
new DateTimeType(historyRecordDTO.getStart().split("\\+")[0].split("\\-")[0]));
DateTimeType start = new DateTimeType(historyRecordDTO.getStart());
historyRecord.addProperty("start", start.toLocaleZone().format(null));
updateState(CHANNEL_HISTORY_START_TIME, start);
}
if (historyRecordDTO.getEnd() != null) {
historyRecord.addProperty("end", historyRecordDTO.getEnd().split("\\+")[0].split("\\-")[0]);
updateState(CHANNEL_HISTORY_END_TIME,
new DateTimeType(historyRecordDTO.getEnd().split("\\+")[0].split("\\-")[0]));
DateTimeType end = new DateTimeType(historyRecordDTO.getEnd());
historyRecord.addProperty("end", end.toLocaleZone().format(null));
updateState(CHANNEL_HISTORY_END_TIME, end);
}
if (historyRecordDTO.getDuration() != null) {
long duration = TimeUnit.SECONDS.toMinutes(historyRecordDTO.getDuration().longValue());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* 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.miio.internal.handler;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.io.IOException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.Timeout.ThreadMode;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.binding.miio.internal.MiIoBindingConstants;
import org.openhab.binding.miio.internal.MiIoCommand;
import org.openhab.binding.miio.internal.MiIoSendCommand;
import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
import org.openhab.binding.miio.internal.cloud.CloudConnector;
import org.openhab.binding.miio.internal.cloud.MiCloudException;
import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.type.ChannelTypeRegistry;

import com.google.gson.JsonParser;

/**
* Test case for {@link MiIoVacuumHandler}
*
* @author Marcel Verpaalen - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class MiIoVacuumHandlerTest {

private @NonNullByDefault({}) MiIoVacuumHandler miIoHandler;
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;

private @Mock @NonNullByDefault({}) CloudConnector cloudConnector;
private @Mock @NonNullByDefault({}) MiIoDatabaseWatchService miIoDatabaseWatchService;
private @Mock @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
private @Mock @NonNullByDefault({}) Thing thing;
private @Mock @NonNullByDefault({}) MiIoAsyncCommunication connection;
private @NonNullByDefault({}) @Mock TranslationProvider translationProvider;
private @NonNullByDefault({}) @Mock LocaleProvider localeProvider;

private final Configuration configuration = new Configuration();
private ThingUID thingUID = new ThingUID(MiIoBindingConstants.THING_TYPE_VACUUM, "TestThing");

@BeforeEach
public void setUp() throws IOException, MiCloudException {
configuration.put(MiIoBindingConstants.PROPERTY_HOST_IP, "localhost");
configuration.put(MiIoBindingConstants.PROPERTY_TOKEN, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
configuration.put(MiIoBindingConstants.PROPERTY_DID, "AABBCCDDEEFF");
configuration.put(MiIoBindingConstants.PROPERTY_CLOUDSERVER, "fake");
configuration.put("communication", "cloud");

when(thing.getConfiguration()).thenReturn(configuration);
when(thing.getUID()).thenReturn(thingUID);
when(thing.getThingTypeUID()).thenReturn(MiIoBindingConstants.THING_TYPE_VACUUM);
when(cloudConnector.sendRPCCommand(any(), any(), any())).thenReturn("{\"result\":\"triggerError\"}");
lenient().when(callback.isChannelLinked(any())).thenReturn(true);

miIoHandler = new MiIoVacuumHandler(thing, miIoDatabaseWatchService, cloudConnector, channelTypeRegistry,
translationProvider, localeProvider);

miIoHandler.setCallback(callback);
}

@AfterEach
public void after() {
miIoHandler.dispose();
}

@Test
public void testInitializeShouldCallTheCallback() throws InterruptedException {
miIoHandler.initialize();
ArgumentCaptor<ThingStatusInfo> statusInfoCaptor = ArgumentCaptor.forClass(ThingStatusInfo.class);
verify(callback).statusUpdated(eq(thing), statusInfoCaptor.capture());
ThingStatusInfo thingStatusInfo = statusInfoCaptor.getValue();
assertEquals(ThingStatus.OFFLINE, thingStatusInfo.getStatus(), "Device should be OFFLINE");
}

@Test
@Timeout(value = 30, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
public void TestCleanRecord() {
miIoHandler.initialize();
// prepare a CLEAN_RECORD_GET response object
String cmdString = "{\"id\":7028,\"method\":\"get_clean_record\",\"params\":[1699081963]}";
String jsonResponseTxt = "{\"result\":[[1724174413,1724174459,246,770000,0,0,2,3,60]],\"id\":7028}";

MiIoSendCommand response = new MiIoSendCommand(13, MiIoCommand.CLEAN_RECORD_GET,
JsonParser.parseString(cmdString).getAsJsonObject(), "", "");
response.setResponse(JsonParser.parseString(jsonResponseTxt).getAsJsonObject());
miIoHandler.onMessageReceived(response);

verify(callback, description("Test the start time parsing")).stateUpdated(
eq(new ChannelUID(thingUID, MiIoBindingConstants.CHANNEL_HISTORY_START_TIME)),
eq(new DateTimeType(ZonedDateTime.parse("2024-08-20T19:20:13+02:00")).toZone(ZoneId.systemDefault())));

verify(callback, description("Test the end time parsing")).stateUpdated(
eq(new ChannelUID(thingUID, MiIoBindingConstants.CHANNEL_HISTORY_END_TIME)),
eq(new DateTimeType(ZonedDateTime.parse("2024-08-20T19:20:59+02:00")).toZone(ZoneId.systemDefault())));

verify(callback, description("Test the duration parsing")).stateUpdated(
eq(new ChannelUID(thingUID, MiIoBindingConstants.CHANNEL_HISTORY_DURATION)),
eq(new QuantityType<>(4, Units.MINUTE)));
}

@Test
@Timeout(value = 30, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
public void TestCleanSummary() {
miIoHandler.initialize();

ThingStatusInfo ts = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "I fake to be online");
when(thing.getStatusInfo()).thenReturn(ts);

// prepare a CLEAN_SUMMARY_GET response object
String cmdString = "{\"id\":114,\"method\":\"get_clean_summary\",\"params\":[]}";
String jsonResponseTxt = "{\"id\":114,\"result\":{\"clean_time\":109968,\"clean_area\":1694875000,\"clean_count\":51,\"dust_collection_count\":48,\"records\":[1699081963,1698999875,1698126572,1697463736,1697031817,1696486642,1696320557,1696253060,1695833343,1695821201,1695619374,1695476013,1695457865,1695274110,1695014622,1694876238,1694860994,1694755927,1694526730,1694237806]}}";

MiIoSendCommand response = new MiIoSendCommand(13, MiIoCommand.CLEAN_SUMMARY_GET,
JsonParser.parseString(cmdString).getAsJsonObject(), "", "");
response.setResponse(JsonParser.parseString(jsonResponseTxt).getAsJsonObject());
miIoHandler.onMessageReceived(response);

verify(callback, description("Test clean time")).stateUpdated(
eq(new ChannelUID(thingUID, MiIoBindingConstants.CHANNEL_HISTORY_TOTALTIME)),
eq(new QuantityType<>(TimeUnit.MINUTES.convert(109968, TimeUnit.SECONDS), Units.MINUTE)));

verify(callback, description("Test the area parsing")).stateUpdated(
eq(new ChannelUID(thingUID, MiIoBindingConstants.CHANNEL_HISTORY_TOTALAREA)),
eq(new QuantityType<>(1694.875, SIUnits.SQUARE_METRE)));
}
}

0 comments on commit 0d2857d

Please sign in to comment.