Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DateTime handling to ItemStateConditionHandler #3138

Merged
merged 1 commit into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
import org.openhab.core.automation.internal.module.handler.ThingStatusTriggerHandler;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.items.ItemRegistry;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
Expand Down Expand Up @@ -73,17 +74,18 @@ public class CoreModuleHandlerFactory extends BaseModuleHandlerFactory implement
CompareConditionHandler.MODULE_TYPE, SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID,
RuleEnablementActionHandler.UID, RunRuleActionHandler.UID);

private ItemRegistry itemRegistry;
private EventPublisher eventPublisher;

private BundleContext bundleContext;
private final ItemRegistry itemRegistry;
private final TimeZoneProvider timeZoneProvider;
private final EventPublisher eventPublisher;
private final BundleContext bundleContext;

@Activate
public CoreModuleHandlerFactory(BundleContext bundleContext, final @Reference EventPublisher eventPublisher,
final @Reference ItemRegistry itemRegistry) {
final @Reference ItemRegistry itemRegistry, final @Reference TimeZoneProvider timeZoneProvider) {
this.bundleContext = bundleContext;
this.eventPublisher = eventPublisher;
this.itemRegistry = itemRegistry;
this.timeZoneProvider = timeZoneProvider;
}

@Override
Expand Down Expand Up @@ -126,7 +128,8 @@ public Collection<String> getTypes() {
} else if (module instanceof Condition) {
// Handle conditions
if (ItemStateConditionHandler.ITEM_STATE_CONDITION.equals(moduleTypeUID)) {
return new ItemStateConditionHandler((Condition) module, ruleUID, bundleContext, itemRegistry);
return new ItemStateConditionHandler((Condition) module, ruleUID, bundleContext, itemRegistry,
timeZoneProvider);
} else if (GenericEventConditionHandler.MODULETYPE_ID.equals(moduleTypeUID)) {
return new GenericEventConditionHandler((Condition) module);
} else if (CompareConditionHandler.MODULE_TYPE.equals(moduleTypeUID)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
*/
package org.openhab.core.automation.internal.module.handler;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
Expand All @@ -24,11 +29,13 @@
import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter;
import org.openhab.core.events.EventSubscriber;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemAddedEvent;
import org.openhab.core.items.events.ItemRemovedEvent;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
Expand Down Expand Up @@ -66,12 +73,14 @@ public class ItemStateConditionHandler extends BaseConditionModuleHandler implem
private final BundleContext bundleContext;
private final Set<String> types;
private final ServiceRegistration<?> eventSubscriberRegistration;
private final TimeZoneProvider timeZoneProvider;

public ItemStateConditionHandler(Condition condition, String ruleUID, BundleContext bundleContext,
ItemRegistry itemRegistry) {
ItemRegistry itemRegistry, TimeZoneProvider timeZoneProvider) {
super(condition);
this.itemRegistry = itemRegistry;
this.bundleContext = bundleContext;
this.timeZoneProvider = timeZoneProvider;
this.itemName = (String) module.getConfiguration().get(ITEM_NAME);
this.types = Set.of(ItemAddedEvent.TYPE, ItemRemovedEvent.TYPE);
this.ruleUID = ruleUID;
Expand Down Expand Up @@ -152,7 +161,11 @@ private boolean lessThanOrEqualsToItemState(String itemName, String state) throw
Item item = itemRegistry.getItem(itemName);
State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state);
State itemState = item.getState();
if (itemState instanceof QuantityType) {
if (itemState instanceof DateTimeType) {
ZonedDateTime itemTime = ((DateTimeType) itemState).getZonedDateTime();
ZonedDateTime compareTime = getCompareTime(state);
return itemTime.compareTo(compareTime) <= 0;
} else if (itemState instanceof QuantityType) {
QuantityType qtState = (QuantityType) itemState;
if (compareState instanceof DecimalType) {
// allow compareState without unit -> implicitly assume its the same as the one from the
Expand Down Expand Up @@ -187,7 +200,11 @@ private boolean greaterThanOrEqualsToItemState(String itemName, String state) th
Item item = itemRegistry.getItem(itemName);
State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state);
State itemState = item.getState();
if (itemState instanceof QuantityType) {
if (itemState instanceof DateTimeType) {
ZonedDateTime itemTime = ((DateTimeType) itemState).getZonedDateTime();
ZonedDateTime compareTime = getCompareTime(state);
return itemTime.compareTo(compareTime) >= 0;
} else if (itemState instanceof QuantityType) {
QuantityType qtState = (QuantityType) itemState;
if (compareState instanceof DecimalType) {
// allow compareState without unit -> implicitly assume its the same as the one from the
Expand Down Expand Up @@ -249,4 +266,37 @@ public boolean apply(Event event) {
logger.trace("->FILTER: {}:{}", event.getTopic(), itemName);
return event.getTopic().contains("openhab/items/" + itemName + "/");
}

private ZonedDateTime getCompareTime(String input) {
if (input.isBlank()) {
// no parameter given, use now
return ZonedDateTime.now();
}
try {
return ZonedDateTime.parse(input);
} catch (DateTimeParseException ignored) {
}
try {
return LocalDateTime.parse(input, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.atZone(timeZoneProvider.getTimeZone());
} catch (DateTimeParseException ignored) {
}
try {
int dayPosition = input.indexOf("D");
if (dayPosition == -1) {
// no date in string, add period symbol and time separator
return ZonedDateTime.now().plus(Duration.parse("PT" + input));
} else if (dayPosition == input.length() - 1) {
// day is the last symbol, only add the period symbol
return ZonedDateTime.now().plus(Duration.parse("P" + input));
} else {
// add period symbol and time separator
return ZonedDateTime.now().plus(Duration
.parse("P" + input.substring(0, dayPosition + 1) + "T" + input.substring(dayPosition + 1)));
}
} catch (DateTimeParseException e) {
logger.warn("Couldn't get a comparable time from '{}', using now", input);
}
return ZonedDateTime.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
Expand All @@ -32,15 +33,18 @@
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.util.ConditionBuilder;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemAddedEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.items.events.ItemRemovedEvent;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
Expand Down Expand Up @@ -76,6 +80,10 @@ public ParameterSet(String itemType, String comparisonState, State itemState, bo
item = new DimmerItem(ITEM_NAME);
((DimmerItem) item).setState(itemState);
break;
case "DateTime":
item = new DateTimeItem(ITEM_NAME);
((DateTimeItem) item).setState(itemState);
break;
default:
throw new IllegalArgumentException();
}
Expand Down Expand Up @@ -112,7 +120,9 @@ public static Collection<Object[]> greaterThanParameters() {
{ new ParameterSet("Number", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number", "5 °C", new QuantityType<>(5, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Dimmer", "20", new PercentType(40), true) }, //
{ new ParameterSet("Dimmer", "20", new PercentType(20), false) } });
{ new ParameterSet("Dimmer", "20", new PercentType(20), false) }, //
{ new ParameterSet("DateTime", "-1H", new DateTimeType(), true) }, //
{ new ParameterSet("DateTime", "1D1M", new DateTimeType(), false) } });
}

public static Collection<Object[]> greaterThanOrEqualsParameters() {
Expand All @@ -133,7 +143,8 @@ public static Collection<Object[]> greaterThanOrEqualsParameters() {
{ new ParameterSet("Number", "0 °C", new QuantityType<>(32, ImperialUnits.FAHRENHEIT), true) }, //
{ new ParameterSet("Number", "32 °F", new QuantityType<>(0, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Dimmer", "20", new PercentType(40), true) }, //
{ new ParameterSet("Dimmer", "40", new PercentType(20), false) } });
{ new ParameterSet("Dimmer", "40", new PercentType(20), false) }, //
{ new ParameterSet("DateTime", "2000-01-01T12:05:00", new DateTimeType(), true) } });
}

public static Collection<Object[]> lessThanParameters() {
Expand All @@ -148,7 +159,10 @@ public static Collection<Object[]> lessThanParameters() {
{ new ParameterSet("Number", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number", "5 °C", new QuantityType<>(4, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Dimmer", "40", new PercentType(20), true) }, //
{ new ParameterSet("Dimmer", "20", new PercentType(20), false) } });
{ new ParameterSet("Dimmer", "20", new PercentType(20), false) }, //
{ new ParameterSet("DateTime", "-1D", new DateTimeType(), false) }, //
{ new ParameterSet("DateTime", "1D5M", new DateTimeType(), true) }, //
{ new ParameterSet("DateTime", "2050-01-01T12:05:00+01:00", new DateTimeType(), true) } });
}

public static Collection<Object[]> lessThanOrEqualsParameters() {
Expand All @@ -169,7 +183,8 @@ public static Collection<Object[]> lessThanOrEqualsParameters() {
{ new ParameterSet("Number", "0 °C", new QuantityType<>(32, ImperialUnits.FAHRENHEIT), true) }, //
{ new ParameterSet("Number", "32 °F", new QuantityType<>(0, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Dimmer", "20", new PercentType(40), false) }, //
{ new ParameterSet("Dimmer", "40", new PercentType(20), true) } });
{ new ParameterSet("Dimmer", "40", new PercentType(20), true) }, //
{ new ParameterSet("DateTime", "", new DateTimeType(), true) } });
}

private static final String ITEM_NAME = "myItem";
Expand All @@ -178,11 +193,13 @@ public static Collection<Object[]> lessThanOrEqualsParameters() {

private @NonNullByDefault({}) @Mock ItemRegistry mockItemRegistry;
private @NonNullByDefault({}) @Mock BundleContext mockBundleContext;
private @NonNullByDefault({}) @Mock TimeZoneProvider mockTimeZoneProvider;

@BeforeEach
public void setup() throws ItemNotFoundException {
when(mockItemRegistry.getItem(ITEM_NAME)).thenAnswer(i -> item);
when(mockItemRegistry.get(ITEM_NAME)).thenAnswer(i -> item);
when(mockTimeZoneProvider.getTimeZone()).thenReturn(ZoneId.systemDefault());
}

public Item getItem() {
Expand Down Expand Up @@ -276,7 +293,8 @@ private ItemStateConditionHandler initItemStateConditionHandler(String operator,
.withId("conditionId") //
.withTypeUID(ItemStateConditionHandler.ITEM_STATE_CONDITION) //
.withConfiguration(configuration);
return new ItemStateConditionHandler(builder.build(), "", mockBundleContext, mockItemRegistry);
return new ItemStateConditionHandler(builder.build(), "", mockBundleContext, mockItemRegistry,
mockTimeZoneProvider);
}

@Test
Expand All @@ -295,7 +313,7 @@ public void itemMessagesAreLogged() {
// missing on creation
when(mockItemRegistry.get(ITEM_NAME)).thenReturn(null);
ItemStateConditionHandler handler = new ItemStateConditionHandler(condition, "foo", mockBundleContext,
mockItemRegistry);
mockItemRegistry, mockTimeZoneProvider);
assertLogMessage(ItemStateConditionHandler.class, LogLevel.WARN,
"Item 'myItem' needed for rule 'foo' is missing. Condition 'conditionId' will not work.");

Expand Down