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

Provide a trigger for thing Online/Offline status in rule. #3001

Merged
merged 14 commits into from Apr 8, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume a break is missing here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks. I'll create a new PR.

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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing break

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll create a new PR.

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'
;