Skip to content

Commit

Permalink
Merge pull request #174 from querdenker2k/add-persist-with-time
Browse files Browse the repository at this point in the history
add persist
  • Loading branch information
seaside1 authored Oct 5, 2023
2 parents 58b51c1 + ef64bfc commit 4d04520
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 16 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ Beta, still major changes.

- Not supporting OH4 GUI rules

### Database

- JRule persist is a utility for supporting `org.openhab.core.persistence.ModifiablePersistenceService`. Currently these Persistance Services are supported:
- Influx
- JDBC
- InMemory

# Getting started

1. Install the addon by either
Expand Down
30 changes: 30 additions & 0 deletions doc/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
+ [Example 39 - HTTP requests](#example-39---http-requests)
+ [Example 40 - Delay rule execution](#example-40---delay-rule-execution)
+ [Example 41 - Get Metadata and Tags](#example-41---get-metadata-and-tags)
+ [Example 42 - Persist future data](#example-42---persist-future-data)

### Example 1 - Invoke another item Switch from rule

Expand Down Expand Up @@ -1064,3 +1065,32 @@ public class DemoRule extends JRule {
}
}
```

## Example 42 - Persist future data

Use case: Persist future data for e.g. Tibber future prices

```java
package org.openhab.automation.jrule.rules.user;

import org.openhab.automation.jrule.rules.JRuleName;
import org.openhab.automation.jrule.rules.JRuleWhenItemReceivedCommand;
import org.openhab.automation.jrule.rules.JRule;
import org.openhab.automation.jrule.rules.JRuleDelayed;

public class DemoRule extends JRule {
@JRuleName("persist new tibber prices")
@JRuleWhenTimeTrigger(hours = 13, minutes = 30)
public void persistNewPrices() {
var availPrices = getPrices();
availPrices.forEach(t -> {
ZonedDateTime zonedDateTime = t.getStartsAt().atZone(ZoneId.systemDefault());
Optional<JRuleValue> historicState = JRuleItems.Tibber_Hourly_Cost_Future.getStateAt(zonedDateTime, PERSISTENCE);
logDebug("adding available price for: {}, existing: {}", zonedDateTime, historicState);
if (historicState.isEmpty()) {
JRuleItems.Tibber_Hourly_Cost_Future.persist(new JRuleDecimalValue(t.getTotal()), zonedDateTime, PERSISTENCE);
}
});
}
}
```
24 changes: 12 additions & 12 deletions src/main/history/dependencies.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:features xmlns:ns2="http://karaf.apache.org/xmlns/features/v1.6.0" name="org.openhab.automation.jrule-4.0.0-SNAPSHOT">
<ns2:feature version="0.0.0">
<ns2:feature>openhab-runtime-base</ns2:feature>
<ns2:feature>wrap</ns2:feature>
<ns2:bundle>mvn:javax.el/javax.el-api/2.2.4</ns2:bundle>
<ns2:bundle>mvn:org.freemarker/freemarker/2.3.32</ns2:bundle>
<ns2:bundle>mvn:org.openhab.addons.bundles/org.openhab.automation.jrule/4.0.0-SNAPSHOT</ns2:bundle>
<ns2:bundle>wrap:mvn:javax.servlet/jsp-api/2.0</ns2:bundle>
<ns2:bundle>wrap:mvn:javax.servlet/servlet-api/2.4</ns2:bundle>
<ns2:bundle>wrap:mvn:org.lastnpe.eea/eea-all/2.2.1</ns2:bundle>
</ns2:feature>
</ns2:features>
<features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="org.openhab.automation.jrule-4.0.0-SNAPSHOT">
<feature version="0.0.0">
<feature>openhab-runtime-base</feature>
<feature>wrap</feature>
<bundle>mvn:javax.el/javax.el-api/2.2.4</bundle>
<bundle>mvn:org.freemarker/freemarker/2.3.32</bundle>
<bundle>mvn:org.openhab.addons.bundles/org.openhab.automation.jrule/4.0.0-SNAPSHOT</bundle>
<bundle>wrap:mvn:javax.servlet/jsp-api/2.0</bundle>
<bundle>wrap:mvn:javax.servlet/servlet-api/2.4</bundle>
<bundle>wrap:mvn:org.lastnpe.eea/eea-all/2.2.1</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.MetadataRegistry;
import org.openhab.core.persistence.PersistenceServiceRegistry;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingRegistry;
Expand Down Expand Up @@ -63,10 +64,12 @@ public JRuleFactory(Map<String, Object> properties, final @Reference JRuleEventS
final @Reference ThingManager thingManager, final @Reference EventPublisher eventPublisher,
final @Reference VoiceManager voiceManager, final ComponentContext componentContext,
final @Reference CronScheduler cronScheduler, final @Reference MetadataRegistry metadataRegistry,
@Reference JRuleRuleProvider ruleProvider) {
final @Reference JRuleRuleProvider ruleProvider,
@Reference final PersistenceServiceRegistry persistenceServiceRegistry) {
JRuleConfig config = new JRuleConfig(properties);
config.initConfig();
jRuleEngine = JRuleEngine.get();
jRuleEngine.setPersistenceServiceRegistry(persistenceServiceRegistry);
jRuleEngine.setConfig(config);
jRuleEngine.setItemRegistry(itemRegistry);
jRuleEngine.setCronScheduler(cronScheduler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemEvent;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.persistence.PersistenceServiceRegistry;
import org.openhab.core.scheduler.CronScheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -109,6 +110,7 @@ public class JRuleEngine implements PropertyChangeListener {
private static volatile JRuleEngine instance;

private JRuleRuleProvider ruleProvider;
private PersistenceServiceRegistry persistenceServiceRegistry;

public static JRuleEngine get() {
if (instance == null) {
Expand Down Expand Up @@ -557,4 +559,12 @@ private void invokeDelayed(JRuleExecutionContext context, JRuleEvent event,
public void setRuleProvider(JRuleRuleProvider ruleProvider) {
this.ruleProvider = ruleProvider;
}

public void setPersistenceServiceRegistry(PersistenceServiceRegistry persistenceServiceRegistry) {
this.persistenceServiceRegistry = persistenceServiceRegistry;
}

public PersistenceServiceRegistry getPersistenceServiceRegistry() {
return persistenceServiceRegistry;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,21 @@ public Optional<JRuleValue> getHistoricState(ZonedDateTime timestamp, String per
return JRulePersistenceExtensions.historicState(name, timestamp, persistenceServiceId);
}

@Override
public Optional<JRuleValue> getStateAt(ZonedDateTime timestamp, String persistenceServiceId) {
return JRulePersistenceExtensions.stateAt(name, timestamp, persistenceServiceId);
}

@Override
public Optional<State> getPreviousState(boolean skipEquals, String persistenceServiceId) {
return JRulePersistenceExtensions.previousState(name, skipEquals, persistenceServiceId);
}

@Override
public void persist(JRuleValue state, ZonedDateTime time, String persistenceServiceId) {
JRulePersistenceExtensions.persist(name, time, state, persistenceServiceId);
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand All @@ -127,4 +137,9 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(name, label, type, id);
}

@Override
public String toString() {
return "%s:%s".formatted(name, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.openhab.automation.jrule.items.metadata.JRuleItemMetadata;

/**
* The {@link JRuleInternalColorGroupItem} Items
* The {@link JRuleInternalNumberGroupItem} Items
*
* @author Arne Seime - Initial contribution
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@
import java.util.Optional;

import org.openhab.automation.jrule.exception.JRuleItemNotFoundException;
import org.openhab.automation.jrule.exception.JRuleRuntimeException;
import org.openhab.automation.jrule.internal.engine.JRuleEngine;
import org.openhab.automation.jrule.internal.handler.JRuleEventHandler;
import org.openhab.automation.jrule.rules.value.JRuleValue;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.persistence.ModifiablePersistenceService;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.PersistenceServiceRegistry;
import org.openhab.core.persistence.QueryablePersistenceService;
import org.openhab.core.persistence.extensions.PersistenceExtensions;
import org.openhab.core.types.State;

Expand All @@ -32,6 +39,46 @@
* @author Arne Seime - Initial contribution
*/
class JRulePersistenceExtensions {
public static void persist(String itemName, ZonedDateTime timestamp, JRuleValue state, String serviceId) {
Item item = getItem(itemName);
JRuleEngine jRuleEngine = JRuleEngine.get();
PersistenceServiceRegistry persistenceServiceRegistry = jRuleEngine.getPersistenceServiceRegistry();
PersistenceService persistenceService = persistenceServiceRegistry
.get(Optional.ofNullable(serviceId).orElse(persistenceServiceRegistry.getDefaultId()));
if (persistenceService instanceof ModifiablePersistenceService modifiablePersistenceService) {
modifiablePersistenceService.store(item, timestamp, state.toOhState());
} else {
throw new JRuleRuntimeException("cannot persist item state, persistence service (%s) not modifiable"
.formatted(persistenceService.getId()));
}
}

public static Optional<JRuleValue> stateAt(String itemName, ZonedDateTime timestamp, String serviceId) {
Item item = getItem(itemName);
JRuleEngine jRuleEngine = JRuleEngine.get();
PersistenceServiceRegistry persistenceServiceRegistry = jRuleEngine.getPersistenceServiceRegistry();
PersistenceService persistenceService = persistenceServiceRegistry
.get(Optional.ofNullable(serviceId).orElse(persistenceServiceRegistry.getDefaultId()));
if (persistenceService instanceof QueryablePersistenceService queryablePersistenceService) {
FilterCriteria filter = new FilterCriteria();
filter.setBeginDate(timestamp.minusSeconds(1));
filter.setEndDate(timestamp.plusSeconds(1));
filter.setItemName(item.getName());
filter.setPageSize(1);
filter.setOrdering(FilterCriteria.Ordering.DESCENDING);
Iterable<HistoricItem> result = queryablePersistenceService.query(filter);
if (result.iterator().hasNext()) {
return Optional.of(result.iterator().next()).map(HistoricItem::getState)
.map(state -> JRuleEventHandler.get().toValue(state));
} else {
return Optional.empty();
}
} else {
throw new JRuleRuntimeException("cannot get state at for item, persistence service (%s) not queryable"
.formatted(persistenceService.getId()));
}
}

public static Optional<JRuleValue> historicState(String itemName, ZonedDateTime timestamp) {
return historicState(itemName, timestamp, null);
}
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/openhab/automation/jrule/items/JRuleItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,24 @@ default Optional<JRuleValue> getHistoricState(ZonedDateTime timestamp) {

Optional<JRuleValue> getHistoricState(ZonedDateTime timestamp, String persistenceServiceId);

default Optional<JRuleValue> getStateAt(ZonedDateTime timestamp) {
return getStateAt(timestamp, null);
}

Optional<JRuleValue> getStateAt(ZonedDateTime timestamp, String persistenceServiceId);

default Optional<State> getPreviousState(boolean skipEquals) {
return getPreviousState(skipEquals, null);
}

Optional<State> getPreviousState(boolean skipEquals, String persistenceServiceId);

default void persist(JRuleValue state, ZonedDateTime time) {
persist(state, time, null);
}

void persist(JRuleValue state, ZonedDateTime time, String persistenceServiceId);

default boolean isGroup() {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ public void persistenceAllTypes() throws IOException, InterruptedException {
// verifyDimmer();
}

@Test
public void persistFuture() throws IOException, InterruptedException {
sendCommand(TestPersistence.ITEM_TRIGGER_RULE, TestPersistence.COMMMAND_PERISTENCE_IN_FUTURE);
verifyRuleWasExecuted(TestPersistence.NAME_PERSIST_IN_FUTURE);
verifyNoError();

Thread.sleep(3000);

sendCommand(TestPersistence.ITEM_TRIGGER_RULE, TestPersistence.COMMMAND_QUERY_IN_FUTURE);
verifyRuleWasExecuted(TestPersistence.NAME_QUERY_IN_FUTURE);
verifyNoError();

verifyLogEntry("now: 10");
verifyLogEntry("now +1: 20");
verifyLogEntry("now +2: 30");

verifyLogEntry("now stateAt: 10");
verifyLogEntry("now stateAt +1: 20");
verifyLogEntry("now stateAt +2: 30");
}

private void verifyNumber() {
verifyPersistence("historicState", "Number_To_Persist", 7, null, 10);
verifyPersistence("historicState", "Number_To_Persist", 5, 20.0, 10);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public abstract class JRuleITBase {
"ghcr.io/shopify/toxiproxy:2.5.0").withNetworkAliases("mqtt").withNetwork(network).dependsOn(mqttContainer)
.withReuse(true);

private static final GenericContainer<?> influxDbContainer = new GenericContainer<>("influxdb:2.0")
protected static final GenericContainer<?> influxDbContainer = new GenericContainer<>("influxdb:2.0")
.withEnv("DOCKER_INFLUXDB_INIT_MODE", "setup").withEnv("DOCKER_INFLUXDB_INIT_USERNAME", "admin")
.withEnv("DOCKER_INFLUXDB_INIT_PASSWORD", "influxdb").withEnv("DOCKER_INFLUXDB_INIT_ORG", "openhab")
.withEnv("DOCKER_INFLUXDB_INIT_BUCKET", "autogen").withEnv("DOCKER_INFLUXDB_INIT_RETENTION", "1w")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,39 @@ public class TestPersistence extends JRule {
public static final String ITEM_LOCATION_TO_PERSIST = "Location_To_Persist";
public static final String ITEM_TRIGGER_RULE = "Trigger_Rule";
public static final String NAME_PERSIST_ALL_TYPES = "persist all types";
public static final String COMMMAND_PERISTENCE = "peristence";
public static final String NAME_PERSIST_IN_FUTURE = "persist in future";
public static final String NAME_QUERY_IN_FUTURE = "query in future";
public static final String COMMMAND_PERISTENCE = "persistence";
public static final String COMMMAND_PERISTENCE_IN_FUTURE = "persistence_in_future";
public static final String COMMMAND_QUERY_IN_FUTURE = "query_in_future";
public static final String PERSISTENCE_SERVICE_ID = "influxdb";
public static final String ITEM_NUMBER_TO_PERSIST_FUTURE = "Number_To_Persist_Future";

private final ZonedDateTime now = ZonedDateTime.now();

@JRuleName(NAME_PERSIST_IN_FUTURE)
@JRuleWhenItemReceivedCommand(item = ITEM_TRIGGER_RULE, condition = @JRuleCondition(eq = COMMMAND_PERISTENCE_IN_FUTURE))
public void persistFuture() {
JRuleNumberItem jRuleNumberItem = JRuleNumberItem.forName(ITEM_NUMBER_TO_PERSIST_FUTURE);

jRuleNumberItem.persist(new JRuleDecimalValue(10), now, PERSISTENCE_SERVICE_ID);
jRuleNumberItem.persist(new JRuleDecimalValue(20), now.plusHours(1), PERSISTENCE_SERVICE_ID);
jRuleNumberItem.persist(new JRuleDecimalValue(30), now.plusHours(2), PERSISTENCE_SERVICE_ID);
}

@JRuleName(NAME_QUERY_IN_FUTURE)
@JRuleWhenItemReceivedCommand(item = ITEM_TRIGGER_RULE, condition = @JRuleCondition(eq = COMMMAND_QUERY_IN_FUTURE))
public void queryFuture() {
JRuleNumberItem jRuleNumberItem = JRuleNumberItem.forName(ITEM_NUMBER_TO_PERSIST_FUTURE);

logInfo("now: {}", jRuleNumberItem.getHistoricState(now, PERSISTENCE_SERVICE_ID).get());
logInfo("now +1: {}", jRuleNumberItem.getHistoricState(now.plusHours(1), PERSISTENCE_SERVICE_ID).get());
logInfo("now +2: {}", jRuleNumberItem.getHistoricState(now.plusHours(2), PERSISTENCE_SERVICE_ID).get());

logInfo("now stateAt: {}", jRuleNumberItem.getStateAt(now, PERSISTENCE_SERVICE_ID).get());
logInfo("now stateAt +1: {}", jRuleNumberItem.getStateAt(now.plusHours(1), PERSISTENCE_SERVICE_ID).get());
logInfo("now stateAt +2: {}", jRuleNumberItem.getStateAt(now.plusHours(2), PERSISTENCE_SERVICE_ID).get());
}

@JRuleName(NAME_PERSIST_ALL_TYPES)
@JRuleWhenItemReceivedCommand(item = ITEM_TRIGGER_RULE, condition = @JRuleCondition(eq = COMMMAND_PERISTENCE))
Expand Down
2 changes: 2 additions & 0 deletions src/test/resources/docker/conf/items/default.items
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,7 @@ Color Color_To_Persist (InfluxDbPersist)
Location Location_To_Persist (InfluxDbPersist)
Number:Power Quantity_To_Persist (InfluxDbPersist)

Number Number_To_Persist_Future (InfluxDbPersist)


Dimmer Dimmer_With_Tags_And_Metadata ["Control", "Light"] { Speech="SetLightState" [ location="Livingroom" ] }

0 comments on commit 4d04520

Please sign in to comment.