Add time series support for script profile (#4365)

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng
2024-08-31 11:49:46 +02:00
committed by GitHub
parent 8a59844451
commit 72753be8cc
2 changed files with 100 additions and 18 deletions
@@ -13,6 +13,7 @@
package org.openhab.core.automation.module.script.profile;
import java.util.List;
import java.util.Optional;
import javax.script.ScriptException;
@@ -22,11 +23,12 @@ import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.profiles.StateProfile;
import org.openhab.core.thing.profiles.TimeSeriesProfile;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TimeSeries;
import org.openhab.core.types.Type;
import org.openhab.core.types.TypeParser;
import org.openhab.core.types.UnDefType;
@@ -39,7 +41,7 @@ import org.slf4j.LoggerFactory;
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ScriptProfile implements StateProfile {
public class ScriptProfile implements TimeSeriesProfile {
public static final String CONFIG_TO_ITEM_SCRIPT = "toItemScript";
public static final String CONFIG_TO_HANDLER_SCRIPT = "toHandlerScript";
@@ -148,21 +150,35 @@ public class ScriptProfile implements StateProfile {
@Override
public void onStateUpdateFromHandler(State state) {
if (isConfigured) {
String returnValue = executeScript(toItemScript, state);
// special handling for UnDefType, it's not available in the TypeParser
if ("UNDEF".equals(returnValue)) {
callback.sendUpdate(UnDefType.UNDEF);
} else if ("NULL".equals(returnValue)) {
callback.sendUpdate(UnDefType.NULL);
} else if (returnValue != null) {
State newState = TypeParser.parseState(acceptedDataTypes, returnValue);
if (newState != null) {
callback.sendUpdate(newState);
}
transformState(state).ifPresent(callback::sendUpdate);
}
}
@Override
public void onTimeSeriesFromHandler(TimeSeries timeSeries) {
if (isConfigured) {
TimeSeries transformedTimeSeries = new TimeSeries(timeSeries.getPolicy());
timeSeries.getStates().forEach(entry -> {
transformState(entry.state()).ifPresent(transformedState -> {
transformedTimeSeries.add(entry.timestamp(), transformedState);
});
});
if (transformedTimeSeries.size() > 0) {
callback.sendTimeSeries(transformedTimeSeries);
}
}
}
private Optional<State> transformState(State state) {
return Optional.ofNullable(executeScript(toItemScript, state)).map(output -> {
return switch (output) {
case "UNDEF" -> UnDefType.UNDEF;
case "NULL" -> UnDefType.NULL;
default -> TypeParser.parseState(acceptedDataTypes, output);
};
});
}
private @Nullable String executeScript(String script, Type input) {
if (!script.isBlank()) {
try {
@@ -12,7 +12,9 @@
*/
package org.openhab.core.automation.module.script.profile;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -23,6 +25,7 @@ import static org.openhab.core.automation.module.script.profile.ScriptProfile.CO
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_HANDLER_SCRIPT;
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_ITEM_SCRIPT;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -51,6 +54,7 @@ import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TimeSeries;
/**
* The {@link ScriptProfileTest} contains tests for the {@link ScriptProfile}
@@ -84,10 +88,12 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onCommandFromHandler(OnOffType.ON);
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(OnOffType.ON));
scriptProfile.onCommandFromItem(OnOffType.ON);
verify(transformationServiceMock, never()).transform(any(), any());
verify(profileCallback, never()).handleCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
@@ -142,11 +148,13 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
scriptProfile.onCommandFromItem(OnOffType.ON);
scriptProfile.onStateUpdateFromItem(OnOffType.ON);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(OnOffType.ON));
verify(transformationServiceMock, times(4)).transform(any(), any());
verify(transformationServiceMock, times(5)).transform(any(), any());
verify(profileCallback, never()).handleCommand(any());
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
@@ -163,11 +171,13 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
scriptProfile.onCommandFromItem(OnOffType.ON);
scriptProfile.onStateUpdateFromItem(OnOffType.ON);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(OnOffType.ON));
verify(transformationServiceMock, times(4)).transform(any(), any());
verify(transformationServiceMock, times(5)).transform(any(), any());
verify(profileCallback, never()).handleCommand(any());
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
@@ -187,10 +197,14 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
verify(transformationServiceMock, times(4)).transform(any(), any());
TimeSeries timeSeries = createTimeSeries(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(timeSeries);
verify(transformationServiceMock, times(5)).transform(any(), any());
verify(profileCallback, times(2)).handleCommand(OnOffType.OFF);
verify(profileCallback).sendUpdate(OnOffType.OFF);
verify(profileCallback).sendCommand(OnOffType.OFF);
verify(profileCallback).sendTimeSeries(replaceTimeSeries(timeSeries, OnOffType.OFF));
}
@Test
@@ -209,10 +223,14 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
verify(transformationServiceMock, times(2)).transform(any(), any());
TimeSeries timeSeries = createTimeSeries(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(timeSeries);
verify(transformationServiceMock, times(3)).transform(any(), any());
verify(profileCallback, never()).handleCommand(any());
verify(profileCallback).sendUpdate(OnOffType.OFF);
verify(profileCallback).sendCommand(OnOffType.OFF);
verify(profileCallback).sendTimeSeries(replaceTimeSeries(timeSeries, OnOffType.OFF));
}
@Test
@@ -230,11 +248,13 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(DecimalType.ZERO));
verify(transformationServiceMock, times(1)).transform(any(), any());
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
@@ -252,11 +272,13 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(DecimalType.ZERO));
verify(transformationServiceMock, times(1)).transform(any(), any());
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
@@ -275,11 +297,13 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(DecimalType.ZERO));
verify(transformationServiceMock, times(4)).transform(any(), any());
verify(transformationServiceMock, times(5)).transform(any(), any());
verify(profileCallback, never()).handleCommand(any());
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
@@ -297,11 +321,53 @@ public class ScriptProfileTest extends JavaTest {
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
scriptProfile.onCommandFromItem(DecimalType.ZERO);
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(createTimeSeries(DecimalType.ZERO));
verify(transformationServiceMock, times(1)).transform(any(), any());
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
verify(profileCallback, never()).sendUpdate(any());
verify(profileCallback, never()).sendCommand(any());
verify(profileCallback, never()).sendTimeSeries(any());
}
@Test
public void filteredTimeSeriesTest() throws TransformationException {
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
.withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class))
.withHandlerAcceptedCommandTypes(List.of(DecimalType.class)).build();
when(transformationServiceMock.transform(any(), eq("0"))).thenReturn(OnOffType.OFF.toString());
when(transformationServiceMock.transform(any(), eq("1"))).thenReturn(null);
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
transformationServiceMock);
TimeSeries timeSeries = createTimeSeries(DecimalType.ZERO, DecimalType.valueOf("1"), DecimalType.ZERO);
scriptProfile.onTimeSeriesFromHandler(timeSeries);
verify(transformationServiceMock, times(3)).transform(any(), any());
TimeSeries transformedTimeSeries = new TimeSeries(timeSeries.getPolicy());
timeSeries.getStates().forEach(entry -> {
if (entry.state().equals(DecimalType.ZERO)) {
transformedTimeSeries.add(entry.timestamp(), OnOffType.OFF);
}
});
verify(profileCallback).sendTimeSeries(transformedTimeSeries);
}
private TimeSeries createTimeSeries(State... states) {
TimeSeries timeSeries = new TimeSeries(TimeSeries.Policy.ADD);
for (State state : states) {
timeSeries.add(Instant.now(), state);
}
return timeSeries;
}
private TimeSeries replaceTimeSeries(TimeSeries timeSeries, State state) {
TimeSeries newTimeSeries = new TimeSeries(timeSeries.getPolicy());
timeSeries.getStates().forEach(entry -> newTimeSeries.add(entry.timestamp(), state));
return newTimeSeries;
}
private static class ProfileContextBuilder {