Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Provide a trigger for thing Online/Offline status in rule. (#3001)
Browse files Browse the repository at this point in the history
* RunEngineImple subscribes to ThingStatusInfoChangedEvent.
* Add a thing syntax in rule.
* Refactor and add RulesThingRefresher.
* Support to handle thing status update in rule engine.
* Expose ThingStatusInfo via an action.

Signed-off-by: kceiw <hellomao@outlook.com>
  • Loading branch information
kceiw authored and kaikreuzer committed Apr 8, 2017
1 parent 21ddab3 commit 0c51457
Show file tree
Hide file tree
Showing 21 changed files with 541 additions and 61 deletions.
Expand Up @@ -17,4 +17,5 @@
<reference bind="setItemRegistry" cardinality="1..1" interface="org.eclipse.smarthome.core.items.ItemRegistry" name="ItemRegistry" policy="static" unbind="unsetItemRegistry"/>
<reference bind="setModelRepository" cardinality="1..1" interface="org.eclipse.smarthome.model.core.ModelRepository" name="ModelRepository" policy="static" unbind="unsetModelRepository"/>
<reference bind="setScriptEngine" cardinality="1..1" interface="org.eclipse.smarthome.model.script.engine.ScriptEngine" name="ScriptEngine" policy="static" unbind="unsetScriptEngine"/>
<reference bind="setThingRegistry" cardinality="1..1" interface="org.eclipse.smarthome.core.thing.ThingRegistry" name="ThingRegistry" policy="static" unbind="unsetThingRegistry"/>
</scr:component>
Expand Up @@ -30,7 +30,10 @@
import org.eclipse.smarthome.core.items.StateChangeListener;
import org.eclipse.smarthome.core.items.events.ItemCommandEvent;
import org.eclipse.smarthome.core.items.events.ItemStateEvent;
import org.eclipse.smarthome.core.thing.ThingRegistry;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.events.ChannelTriggeredEvent;
import org.eclipse.smarthome.core.thing.events.ThingStatusInfoChangedEvent;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.model.core.ModelRepository;
Expand Down Expand Up @@ -72,6 +75,7 @@ public class RuleEngineImpl implements ItemRegistryChangeListener, StateChangeLi
private ItemRegistry itemRegistry;
private ModelRepository modelRepository;
private ScriptEngine scriptEngine;
private ThingRegistry thingRegistry;

private RuleTriggerManager triggerManager;

Expand Down Expand Up @@ -161,6 +165,14 @@ public void unsetScriptEngine(ScriptEngine scriptEngine) {
this.scriptEngine = null;
}

public void setThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = thingRegistry;
}

public void unsetThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = null;
}

@Override
public void allItemsChanged(Collection<String> oldItemNames) {
// add the current items again
Expand Down Expand Up @@ -222,6 +234,20 @@ private void receiveThingTrigger(ChannelTriggeredEvent event) {
executeRules(rules, event);
}

private void receiveThingStatus(ThingStatusInfoChangedEvent event) {
String thingUid = event.getThingUID().getAsString();
ThingStatus oldStatus = event.getOldStatusInfo().getStatus();
ThingStatus newStatus = event.getStatusInfo().getStatus();

Iterable<Rule> rules = triggerManager.getRules(THINGUPDATE, thingUid, newStatus);
executeRules(rules);

if (oldStatus != newStatus) {
rules = triggerManager.getRules(THINGCHANGE, thingUid, oldStatus, newStatus);
executeRules(rules, oldStatus);
}
}

private void internalItemAdded(Item item) {
if (item instanceof GenericItem) {
GenericItem genericItem = (GenericItem) item;
Expand Down Expand Up @@ -341,6 +367,14 @@ protected synchronized void executeRules(Iterable<Rule> rules, State oldState) {
}
}

protected synchronized void executeRules(Iterable<Rule> rules, ThingStatus oldThingStatus) {
for (Rule rule : rules) {
RuleEvaluationContext context = new RuleEvaluationContext();
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_PREVIOUS_STATE), oldThingStatus.toString());
executeRule(rule, context);
}
}

/**
* we need to be able to deactivate the rule execution, otherwise the Eclipse SmartHome designer would also execute
* the rules.
Expand All @@ -358,7 +392,7 @@ public void updated(Item oldItem, Item item) {
}

private final Set<String> subscribedEventTypes = ImmutableSet.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE,
ChannelTriggeredEvent.TYPE);
ChannelTriggeredEvent.TYPE, ThingStatusInfoChangedEvent.TYPE);

@Override
public Set<String> getSubscribedEventTypes() {
Expand All @@ -376,6 +410,8 @@ public void receive(Event event) {
receiveCommand((ItemCommandEvent) event);
} else if (event instanceof ChannelTriggeredEvent) {
receiveThingTrigger((ChannelTriggeredEvent) event);
} else if (event instanceof ThingStatusInfoChangedEvent) {
receiveThingStatus((ThingStatusInfoChangedEvent) event);
}
}
}
Expand Up @@ -22,6 +22,7 @@

import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.Type;
Expand All @@ -34,6 +35,8 @@
import org.eclipse.smarthome.model.rule.rules.RuleModel;
import org.eclipse.smarthome.model.rule.rules.SystemOnShutdownTrigger;
import org.eclipse.smarthome.model.rule.rules.SystemOnStartupTrigger;
import org.eclipse.smarthome.model.rule.rules.ThingStateChangedEventTrigger;
import org.eclipse.smarthome.model.rule.rules.ThingStateUpdateEventTrigger;
import org.eclipse.smarthome.model.rule.rules.TimerTrigger;
import org.eclipse.smarthome.model.rule.rules.UpdateEventTrigger;
import org.quartz.CronScheduleBuilder;
Expand Down Expand Up @@ -74,13 +77,17 @@ public enum TriggerTypes {
TRIGGER, // fires whenever a trigger is emitted on a channel
STARTUP, // fires when the rule engine bundle starts and once as soon as all required items are available
SHUTDOWN, // fires when the rule engine bundle is stopped
TIMER // fires at a given time
TIMER, // fires at a given time
THINGUPDATE, // fires whenever the thing state is updated.
THINGCHANGE, // fires if the thing state is changed by the update
}

// lookup maps for different triggering conditions
private Map<String, Set<Rule>> updateEventTriggeredRules = Maps.newHashMap();
private Map<String, Set<Rule>> changedEventTriggeredRules = Maps.newHashMap();
private Map<String, Set<Rule>> commandEventTriggeredRules = Maps.newHashMap();
private Map<String, Set<Rule>> thingUpdateEventTriggeredRules = Maps.newHashMap();
private Map<String, Set<Rule>> thingChangedEventTriggeredRules = Maps.newHashMap();
// Maps from channelName -> Rules
private Map<String, Set<Rule>> triggerEventTriggeredRules = Maps.newHashMap();
private Set<Rule> systemStartupTriggeredRules = new CopyOnWriteArraySet<>();
Expand Down Expand Up @@ -133,6 +140,11 @@ public Iterable<Rule> getRules(TriggerTypes type) {
case TRIGGER:
result = Iterables.concat(triggerEventTriggeredRules.values());
break;
case THINGUPDATE:
result = Iterables.concat(thingUpdateEventTriggeredRules.values());
break;
case THINGCHANGE:
result = Iterables.concat(thingChangedEventTriggeredRules.values());
default:
result = Sets.newHashSet();
}
Expand Down Expand Up @@ -221,18 +233,31 @@ public Iterable<Rule> getRules(TriggerTypes triggerType, String channel, String
return result;
}

private Iterable<Rule> getAllRules(TriggerTypes type, String itemName) {
public Iterable<Rule> getRules(TriggerTypes triggerType, String thingUid, ThingStatus state) {
return internalGetThingRules(triggerType, thingUid, null, state);
}

public Iterable<Rule> getRules(TriggerTypes triggerType, String thingUid, ThingStatus oldState,
ThingStatus newState) {
return internalGetThingRules(triggerType, thingUid, oldState, newState);
}

private Iterable<Rule> getAllRules(TriggerTypes type, String name) {
switch (type) {
case STARTUP:
return systemStartupTriggeredRules;
case SHUTDOWN:
return systemShutdownTriggeredRules;
case UPDATE:
return updateEventTriggeredRules.get(itemName);
return updateEventTriggeredRules.get(name);
case CHANGE:
return changedEventTriggeredRules.get(itemName);
return changedEventTriggeredRules.get(name);
case COMMAND:
return commandEventTriggeredRules.get(itemName);
return commandEventTriggeredRules.get(name);
case THINGUPDATE:
return thingUpdateEventTriggeredRules.get(name);
case THINGCHANGE:
return thingChangedEventTriggeredRules.get(name);
default:
return Sets.newHashSet();
}
Expand Down Expand Up @@ -331,6 +356,67 @@ private Iterable<Rule> internalGetRules(TriggerTypes triggerType, Item item, Typ
return result;
}

private Iterable<Rule> internalGetThingRules(TriggerTypes triggerType, String thingUid, ThingStatus oldStatus,
ThingStatus newStatus) {
List<Rule> result = Lists.newArrayList();
Iterable<Rule> rules = getAllRules(triggerType, thingUid);
if (rules == null) {
rules = Lists.newArrayList();
}

switch (triggerType) {
case THINGUPDATE:
for (Rule rule : rules) {
for (EventTrigger t : rule.getEventtrigger()) {
if (t instanceof ThingStateUpdateEventTrigger) {
ThingStateUpdateEventTrigger tt = (ThingStateUpdateEventTrigger) t;
if (tt.getThing().equals(thingUid)) {
String stateString = tt.getState();
if (stateString != null) {
ThingStatus triggerState = ThingStatus.valueOf(stateString);
if (!newStatus.equals(triggerState)) {
continue;
}
}
result.add(rule);
}
}
}
}
break;
case THINGCHANGE:
for (Rule rule : rules) {
for (EventTrigger t : rule.getEventtrigger()) {
if (t instanceof ThingStateChangedEventTrigger) {
ThingStateChangedEventTrigger ct = (ThingStateChangedEventTrigger) t;
if (ct.getThing().equals(thingUid)) {
String oldStatusString = ct.getOldState();
if (oldStatusString != null) {
ThingStatus triggerOldState = ThingStatus.valueOf(oldStatusString);
if (!oldStatus.equals(triggerOldState)) {
continue;
}
}

String newStatusString = ct.getNewState();
if (newStatusString != null) {
ThingStatus triggerNewState = ThingStatus.valueOf(newStatusString);
if (!newStatus.equals(triggerNewState)) {
continue;
}
}
result.add(rule);
}
}
}
}
break;
default:
break;
}
return result;
}

/**
* Removes all rules with a given trigger type from the mapping tables.
*
Expand Down Expand Up @@ -362,6 +448,12 @@ public void clear(TriggerTypes type) {
}
timerEventTriggeredRules.clear();
break;
case THINGUPDATE:
thingUpdateEventTriggeredRules.clear();
break;
case THINGCHANGE:
thingChangedEventTriggeredRules.clear();
break;
}
}

Expand All @@ -376,6 +468,8 @@ public void clearAll() {
clear(COMMAND);
clear(TIMER);
clear(TRIGGER);
clear(THINGUPDATE);
clear(THINGCHANGE);
}

/**
Expand Down Expand Up @@ -430,6 +524,22 @@ public synchronized void addRule(Rule rule) {
triggerEventTriggeredRules.put(eeTrigger.getChannel(), rules);
}
rules.add(rule);
} else if (t instanceof ThingStateUpdateEventTrigger) {
ThingStateUpdateEventTrigger tsuTrigger = (ThingStateUpdateEventTrigger) t;
Set<Rule> rules = thingUpdateEventTriggeredRules.get(tsuTrigger);
if (rules == null) {
rules = new HashSet<Rule>();
thingUpdateEventTriggeredRules.put(tsuTrigger.getThing(), rules);
}
rules.add(rule);
} else if (t instanceof ThingStateChangedEventTrigger) {
ThingStateChangedEventTrigger tscTrigger = (ThingStateChangedEventTrigger) t;
Set<Rule> rules = thingChangedEventTriggeredRules.get(tscTrigger.getThing());
if (rules == null) {
rules = new HashSet<Rule>();
thingChangedEventTriggeredRules.put(tscTrigger.getThing(), rules);
}
rules.add(rule);
}
}
}
Expand Down Expand Up @@ -470,6 +580,15 @@ public void removeRule(TriggerTypes type, Rule rule) {
timerEventTriggeredRules.remove(rule);
removeTimerRule(rule);
break;
case THINGUPDATE:
for (Set<Rule> rules : thingUpdateEventTriggeredRules.values()) {
rules.remove(rule);
}
case THINGCHANGE:
for (Set<Rule> rules : thingChangedEventTriggeredRules.values()) {
rules.remove(rule);
}
break;
}
}

Expand Down Expand Up @@ -497,6 +616,8 @@ public void removeRuleModel(RuleModel ruleModel) {
removeRules(STARTUP, Collections.singletonList(systemStartupTriggeredRules), ruleModel);
removeRules(SHUTDOWN, Collections.singletonList(systemShutdownTriggeredRules), ruleModel);
removeRules(TIMER, Collections.singletonList(timerEventTriggeredRules), ruleModel);
removeRules(THINGUPDATE, thingUpdateEventTriggeredRules.values(), ruleModel);
removeRules(THINGCHANGE, thingChangedEventTriggeredRules.values(), ruleModel);
}

private void removeRules(TriggerTypes type, Collection<? extends Collection<Rule>> ruleSets, RuleModel model) {
Expand Down
Expand Up @@ -29,6 +29,7 @@ Import-Package: com.google.common.collect,
org.eclipse.smarthome.core.library.types,
org.eclipse.smarthome.core.persistence,
org.eclipse.smarthome.core.service,
org.eclipse.smarthome.core.thing,
org.eclipse.smarthome.core.types,
org.eclipse.smarthome.model.core,
org.eclipse.smarthome.model.items,
Expand Down
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014-2016 by the respective copyright holders.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipse.smarthome.model.rule.jvmmodel.rulesthingrefresher">
<implementation class="org.eclipse.smarthome.model.rule.jvmmodel.RulesThingRefresher"/>
<reference bind="setModelRepository" cardinality="1..1" interface="org.eclipse.smarthome.model.core.ModelRepository" name="ModelRepository" policy="dynamic" unbind="unsetModelRepository"/>
<reference bind="setThingRegistry" cardinality="0..1" interface="org.eclipse.smarthome.core.thing.ThingRegistry" name="ThingRegistry" policy="dynamic" unbind="unsetThingRegistry"/>
<reference bind="addActionService" cardinality="0..n" interface="org.eclipse.smarthome.model.script.engine.action.ActionService" name="ActionService" policy="dynamic" unbind="removeActionService"/>
</scr:component>
Expand Up @@ -32,7 +32,9 @@ EventTrigger:
ChangedEventTrigger |
EventEmittedTrigger |
TimerTrigger |
SystemTrigger
SystemTrigger |
ThingStateUpdateEventTrigger |
ThingStateChangedEventTrigger
;

CommandEventTrigger:
Expand Down Expand Up @@ -72,6 +74,13 @@ SystemOnShutdownTrigger:
'System' 'shuts down'
;

ThingStateUpdateEventTrigger:
'Thing' thing=STRING 'received update' (state=ThingValidState)?
;

ThingStateChangedEventTrigger:
'Thing' thing=STRING 'changed' ('from' oldState=ThingValidState)? ('to' newState=ThingValidState)?
;

ItemName :
ID
Expand All @@ -87,4 +96,14 @@ ValidCommand:

ValidTrigger:
ID | Number | STRING
;

ThingValidState:
'UNINITIALIZED' |
'INITIALIZING' |
'UNKNOWN' |
'ONLINE' |
'OFFLINE' |
'REMOVING' |
'REMOVED'
;

0 comments on commit 0c51457

Please sign in to comment.