Skip to content

Commit

Permalink
Add time series support for script profile (openhab#4365)
Browse files Browse the repository at this point in the history
Signed-off-by: Jimmy Tanagra <[email protected]>
  • Loading branch information
jimtng authored Aug 31, 2024
1 parent 8a59844 commit 72753be
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.core.automation.module.script.profile;

import java.util.List;
import java.util.Optional;

import javax.script.ScriptException;

Expand All @@ -22,11 +23,12 @@
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;
Expand All @@ -39,7 +41,7 @@
* @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";
Expand Down Expand Up @@ -148,21 +150,35 @@ public void onCommandFromHandler(Command command) {
@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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +25,7 @@
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;
Expand Down Expand Up @@ -51,6 +54,7 @@
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}
Expand Down Expand Up @@ -84,10 +88,12 @@ public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined(

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());

Expand Down Expand Up @@ -142,11 +148,13 @@ public void scriptExecutionErrorForwardsNoValueToCallback() throws Transformatio
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
Expand All @@ -163,11 +171,13 @@ public void scriptExecutionResultNullForwardsNoValueToCallback() throws Transfor
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
Expand All @@ -187,10 +197,14 @@ public void scriptExecutionResultForwardsTransformedValueToCallback() throws Tra
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
Expand All @@ -209,10 +223,14 @@ public void onlyToItemScriptDoesNotForwardOutboundCommands() throws Transformati
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
Expand All @@ -230,11 +248,13 @@ public void onlyToHandlerCommandScriptDoesNotForwardInboundCommands() throws Tra
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
Expand All @@ -252,11 +272,13 @@ public void onlyToHandlerStateScriptDoesNotForwardInboundCommands() throws Trans
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
Expand All @@ -275,11 +297,13 @@ public void incompatibleStateOrCommandNotForwardedToCallback() throws Transforma
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
Expand All @@ -297,11 +321,53 @@ public void fallbackToToHandlerScriptIfNotToHandlerCommandScript() throws Transf
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 {
Expand Down

0 comments on commit 72753be

Please sign in to comment.