Skip to content
Snippets Groups Projects
Commit 2479587d authored by Manuel Krombholz's avatar Manuel Krombholz Committed by René Schöne
Browse files

Resolve "Add processing frequency option for items"

parent 93fbab4a
Branches
No related tags found
2 merge requests!19dev to master,!11Resolve "Add processing frequency option for items"
......@@ -21,6 +21,8 @@ aspect ItemHandling {
eq ItemWithDoubleState.getStateAsString() = Double.toString(getState());
eq ItemWithStringState.getStateAsString() = getState();
syn LastChanged Item.getLastChanged() = new LastChanged();
//--- getStateAsDouble ---
syn double Item.getStateAsDouble();
// TupleHSB and String work like default
......@@ -285,7 +287,7 @@ aspect ItemHandling {
//--- stateUpdated ---
/**
* Called, whenever the state of an item is updated. Does various things including:
* Called, whenever the state of an item is updated. Does various things (except it has no parent) including:
* <ul>
* <li>Send the new state via MQTT</li>
* <li>Send the new state to Influx DB</li>
......@@ -294,6 +296,7 @@ aspect ItemHandling {
* @param shouldSendState whether to send the new state (currently affects MQTT and Influx)
*/
protected void Item.stateUpdated(boolean shouldSendState) {
if (getParent() == null) { return; }
if (shouldSendState) {
try {
// sendState() refined in MQTT and Influx aspect
......@@ -302,9 +305,13 @@ aspect ItemHandling {
logger.catching(e);
}
}
getItemObserver().apply();
if (this.getLastChanged().checkStateProcessingTime(relevantFrequencySetting())) {
this.getLastChanged().afterStateChangeProcessed();
getItemObserver().apply();
}
}
//--- sendState ---
protected void Item.sendState() throws Exception {
for (MachineLearningModel model : getRelevantInMachineLearningModels()) {
......@@ -459,6 +466,11 @@ aspect ItemHandling {
}
//--- onReceiveStateChange ---
protected void Item.onReceiveStateChange() {
//Instant lastChange = this.
}
......@@ -506,4 +518,6 @@ aspect ItemHandling {
syn String ItemUpdate.getNewStateAsString();
eq ItemUpdateColor.getNewStateAsString() = getNewHSB().toString();
eq ItemUpdateDouble.getNewStateAsString() = Double.toString(getNewValue());
}
aspect LastChanged {
public void LastChanged.afterStateChangeProcessed() {
this.setValue(Instant.now());
}
public boolean LastChanged.checkStateProcessingTime(FrequencySetting FrequencySetting) {
if (FrequencySetting==null) {
return true;
}
double frequency = FrequencySetting.getEventProcessingFrequency();
Instant lastStateChange = this.getValue();
if (lastStateChange==null) {
return true;
}
return lastStateChange.toEpochMilli() + (1/frequency)*1000 < Instant.now().toEpochMilli();
}
}
\ No newline at end of file
......@@ -11,6 +11,48 @@ aspect Navigation {
return result;
}
//--- enclosingGroup ---
inh Group Group.enclosingGroup();
inh Group Item.enclosingGroup();
eq Group.getItem().enclosingGroup() = this;
eq Group.getGroup().enclosingGroup() = this;
eq SmartHomeEntityModel.getGroup().enclosingGroup() = null;
eq SmartHomeEntityModel.getActivityItem().enclosingGroup() = null;
//--- relevantFrequencySetting ---
syn FrequencySetting Group.relevantFrequencySetting() {
// first, use value defined on group itself, if any
if (this.hasFrequencySetting()) {
return this.getFrequencySetting();
}
// recursively use enclosing group and use value from there, if any
Group parent = enclosingGroup();
if (parent != null) {
return parent.relevantFrequencySetting();
}
// if top-level group without FrequencySetting
return null;
}
syn FrequencySetting Item.relevantFrequencySetting() {
// first, use value defined on item itself, if any
if (this.hasFrequencySetting()) {
return this.getFrequencySetting();
}
// use enclosing group and use value from there, if any
Group parent = enclosingGroup();
if (parent != null) {
return parent.relevantFrequencySetting();
}
// if top-level item without FrequencySetting
return null;
}
//--- addItems ---
private void SmartHomeEntityModel.addItems(java.util.List<Item> result, JastAddList<Group> groups) {
groups.forEach(group -> group.getItemList().forEach(item -> result.add(item)));
}
......@@ -56,4 +98,5 @@ aspect Navigation {
eq Root.getRule().getRoot() = this;
eq Root.getUser().getRoot() = this;
eq Root.getLocation().getRoot() = this;
eq Root.getFrequencySetting().getRoot() = this;
}
......@@ -98,9 +98,22 @@ aspect Resolving {
return java.util.Optional.empty();
}
syn java.util.Optional<FrequencySetting> Root.resolveFrequencySetting(String performanceId) {
for (FrequencySetting performance : getFrequencySettingList()) {
if (performance.getLabel().equals(performanceId)) {
return java.util.Optional.of(performance);
}
}
return java.util.Optional.empty();
}
// implementing resolving for relations
refine RefResolverStubs eq StateSyncGroup.resolveTargetItemByToken(String id, int position) {
return getRoot().getSmartHomeEntityModel().resolveItem(id).orElseThrow(() -> new RuntimeException("Item '" + id + "' not found!"));
}
refine RefResolverStubs eq ASTNode.globallyResolveFrequencySettingByToken(String id) {
return getRoot().resolveFrequencySetting(id).orElseThrow(() -> new RuntimeException("FrequencySetting '" + id + "' not found!"));
}
}
......@@ -2,7 +2,8 @@
Rule ::= Condition* Action* ;
abstract Condition ;
ItemStateCheckCondition : Condition ::= ItemStateCheck ;
ItemStateChangeCondition : Condition ::= Item;
ItemStateChangeCondition : Condition ;
rel ItemStateChangeCondition.Item -> Item;
ExpressionCondition : Condition ::= LogicalExpression ;
abstract Action ;
NoopAction : Action ;
......@@ -29,5 +30,6 @@ MultiplyDoubleToStateAction : SetStateAction ::= <Multiplier:double> ;
ItemObserver ::= ;
rel ItemObserver.TriggeredRule* <-> Rule.Observer* ;
FrequencySetting : LabelledModelElement ::= <EventProcessingFrequency:double> ;
StateSyncGroup : Rule ;
rel StateSyncGroup.TargetItem* -> Item;
......@@ -87,6 +87,9 @@ Comment = "//" [^\n\r]+
"incoming" { return sym(Terminals.INCOMING); }
"items" { return sym(Terminals.ITEMS); }
"itemType" { return sym(Terminals.ITEM_TYPE); }
"FrequencySetting" { return sym(Terminals.FREQUENCY_SETTING); }
"performance" { return sym(Terminals.PERFORMANCE); }
"procFreq" { return sym(Terminals.PROCESS_FREQUENCY); }
"label" { return sym(Terminals.LABEL); }
"links" { return sym(Terminals.LINKS); }
"metaData" { return sym(Terminals.META_DATA); }
......
......@@ -41,6 +41,7 @@ Root goal =
| influx_root.ir goal.r {: r.setInfluxRoot(ir); return r; :}
| machine_learning_root.ml goal.r {: r.setMachineLearningRoot(ml); return r; :}
| rule.rule goal.r {: r.addRule(rule); return r; :}
| frequency_setting.ip goal.r {: r.addFrequencySetting(ip); return r; :}
| thing.t {: return eph.createRoot(t); :}
| item.i {: return eph.createRoot(); :}
| group.g {: return eph.createRoot(g); :}
......@@ -53,6 +54,7 @@ Root goal =
| influx_root.ir {: return eph.createRoot(ir); :}
| machine_learning_root.ml {: return eph.createRoot(ml); :}
| rule.rule {: return eph.createRoot(rule); :}
| frequency_setting.ip {: return eph.createRoot(ip); :}
;
%left RB_ROUND;
......@@ -128,28 +130,28 @@ Item item =
| ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new DefaultItem(), ib); :}
;
// ITEM_TYPE Item: id="" label="" state="" category="" topic="" metaData={"key":"value"} ;
// ITEM_TYPE Item: id="" label="" state="" category="" topic="" performance="" metaData={"key":"value"} ;
Item item_body =
ID EQUALS TEXT.n item_body.i {: return eph.setID(i, n); :}
| LABEL EQUALS TEXT.n item_body.i {: i.setLabel(n); return i; :}
| STATE EQUALS TEXT.n item_body.i {: i.setStateFromString(n); return i; :}
| TOPIC EQUALS TEXT.n item_body.i {: return eph.setTopic(i, n); :}
| CATEGORY EQUALS TEXT.n item_body.i {: return eph.setCategory(i, n); :}
| PERFORMANCE EQUALS frequency_setting_ref.ip item_body.i {: i.setFrequencySetting(ip); return i; :}
| META_DATA EQUALS string_map.md item_body.i
{: return eph.setMetaData(i, md); :}
| {: return eph.createItem(); :}
;
Item item_ref =
TEXT.n {: return Item.createRef(n); :}
;
Item item_ref = TEXT.n {: return Item.createRef(n); :};
FrequencySetting frequency_setting_ref = TEXT.n {: return FrequencySetting.createRef(n); :};
Group group =
GROUP COLON group_body.gb SEMICOLON {: return gb; :}
;
// Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation="";
// Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation="" ("","");
// Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] performance="" aggregation="";
// Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] performance="" aggregation="" ("","");
Group group_body =
ID EQUALS TEXT.n group_body.g {: return eph.setID(g, n); :}
| LABEL EQUALS TEXT.n group_body.g {: g.setLabel(n); return g; :}
......@@ -158,6 +160,7 @@ Group group_body =
| AGGREGATION EQUALS TEXT.n group_body.g {: return eph.setSimpleAggregationFunction(g, n); :}
| AGGREGATION EQUALS TEXT.n round_string_list.params group_body.g
{: return eph.setParameterizedAggregationFunction(g, n, params); :}
| PERFORMANCE EQUALS frequency_setting_ref.ip group_body.g {: g.setFrequencySetting(ip); return g; :}
| {: return new Group(); :}
;
......@@ -365,3 +368,14 @@ IntegerKeyMap integer_map_body =
return result;
:}
;
FrequencySetting frequency_setting =
FREQUENCY_SETTING COLON frequency_setting_body.ipb SEMICOLON {: return ipb; :}
;
// FrequencySetting: id="" procFreq="" persFreq="";
FrequencySetting frequency_setting_body =
ID EQUALS TEXT.n frequency_setting_body.ip {: return eph.setID(ip, n); :}
| PROCESS_FREQUENCY EQUALS TEXT.n frequency_setting_body.ip {: ip.setEventProcessingFrequency(Double.parseDouble(n)); return ip; :}
| {: return new FrequencySetting(); :}
;
// ---------------- Main ------------------------------
Root ::= SmartHomeEntityModel User* MqttRoot InfluxRoot MachineLearningRoot Rule* Location* ;
Root ::= SmartHomeEntityModel User* MqttRoot InfluxRoot MachineLearningRoot Rule* Location* FrequencySetting*;
// ---------------- Users ------------------------------
User : LabelledModelElement ;
......
// ---------------- openHAB ------------------------------
SmartHomeEntityModel ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* /ActivityItem:Item/ ;
abstract ModelElement ::= <ID:String> ;
abstract LabelledModelElement : ModelElement ::= <Label:String> ;
abstract DescribableModelElement : LabelledModelElement ::= <Description:String> ;
......@@ -25,10 +26,9 @@ rel Channel.LinkedItem* <-> Item.Channel? ;
Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ;
ParameterDefaultValue ::= <Value:String> ;
abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/;
abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/ /LastChanged/;
rel Item.Category? -> ItemCategory ;
rel Item.FrequencySetting? -> FrequencySetting ;
abstract ItemWithBooleanState : Item ::= <_state:boolean> ;
abstract ItemWithStringState : Item ::= <_state:String> ;
......@@ -51,8 +51,14 @@ ItemMetaData ::= <Key:String> <Value:String> ;
ItemCategory ::= <Name:String> ;
LastChanged ::= <Value:Instant> ;
Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ;
rel Group.FrequencySetting? -> FrequencySetting ;
abstract GroupAggregationFunction ;
SimpleGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:SimpleGroupAggregationFunctionName> ;
ParameterizedGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:ParameterizedGroupAggregationFunctionName>
<Param1:String> <Param2:String> ;
......@@ -24,16 +24,18 @@ public class EraserParserHelper {
private Map<String, Parameter> parameterMap = new HashMap<>();
private Map<String, Item> itemMap = new HashMap<>();
private Map<String, Group> groupMap = new HashMap<>();
private Map<String, FrequencySetting> FrequencySettingMap = new HashMap<>();
private Map<Thing, String> missingThingTypeMap = new HashMap<>();
private Map<Channel, String> missingChannelTypeMap = new HashMap<>();
private Map<Item, String> missingTopicMap = new HashMap<>();
private Map<Item, String> missingItemCategoryMap = new HashMap<>();
private Map<Designator, String> missingItemForDesignator = new HashMap<>();
private Map<Thing, Iterable<String>> missingChannelListMap = new HashMap<>();
private Map<Channel, Iterable<String>> missingItemLinkListMap = new HashMap<>();
private Map<Item, Iterable<String>> missingControllingListMap = new HashMap<>();
private Map<Group, Iterable<String>> missingSubGroupListMap = new HashMap<>();
private Map<Group, Iterable<String>> missingItemListMap = new HashMap<>();
private Map<ThingType, Iterable<String>> missingChannelTypeListMap = new HashMap<>();
......@@ -94,19 +96,20 @@ public class EraserParserHelper {
resolveList(itemMap, missingItemLinkListMap, Channel::addLinkedItem);
resolveList(groupMap, missingSubGroupListMap, Group::addGroup);
resolveList(itemMap, missingItemListMap, this::addItemToGroup);
resolveList(channelTypeMap, missingChannelTypeListMap, ThingType::addChannelType);
resolveList(parameterMap, missingParameterListMap, ThingType::addParameter);
resolveList(itemMap, missingControllingListMap, Item::addControlling);
createUnknownGroupIfNecessary();
createChannelCategories();
createItemCategories();
if (checkUnusedElements) {
checkUnusedElements();
}
this.root.treeResolveAll();
this.root.doFullTraversal();
}
private void addItemToGroup(Group group, Item item) {
......@@ -226,6 +229,12 @@ public class EraserParserHelper {
return thing;
}
public FrequencySetting setID(FrequencySetting FrequencySetting, String id) {
FrequencySetting.setID(id);
FrequencySettingMap.put(id,FrequencySetting);
return FrequencySetting;
}
public ThingType setID(ThingType thingType, String id) {
thingType.setID(id);
thingTypeMap.put(id, thingType);
......@@ -259,6 +268,7 @@ public class EraserParserHelper {
return c;
}
public Channel setLinks(Channel c, StringList linkNames) {
missingItemLinkListMap.put(c, linkNames);
return c;
......@@ -300,6 +310,7 @@ public class EraserParserHelper {
itemWithCorrectType.setID(prototype.getID());
itemWithCorrectType.setLabel(prototype.getLabel());
itemWithCorrectType.setMetaDataList(prototype.getMetaDataList());
itemWithCorrectType.setFrequencySetting(prototype.getFrequencySetting());
if (!(itemWithCorrectType instanceof ActivityItem)) {
String state = prototype.getStateAsString();
itemWithCorrectType.disableSendState();
......@@ -312,7 +323,6 @@ public class EraserParserHelper {
}
moveMissingForRetype(itemWithCorrectType, prototype, missingTopicMap);
moveMissingForRetype(itemWithCorrectType, prototype, missingControllingListMap);
moveMissingForRetype(itemWithCorrectType, prototype, missingItemCategoryMap);
itemMap.put(prototype.getID(), itemWithCorrectType);
......@@ -470,6 +480,12 @@ public class EraserParserHelper {
return result;
}
public Root createRoot(FrequencySetting FrequencySetting) {
Root result = createRoot();
result.addFrequencySetting(FrequencySetting);
return result;
}
//+++ newStuff (to be categorized) +++
public Designator createDesignator(String itemName) {
Designator result = new Designator();
......
......@@ -250,6 +250,7 @@ public class RulesTest {
assertEquals(2, counter.get(item), "Change of item to 7 should not trigger the rule, check2 violated");
}
@Test
public void testStateSyncGroupRewriteStructure() {
// init StateSyncGroup
......@@ -915,6 +916,35 @@ public class RulesTest {
assertEquals(2, counter.get(null), "Rule was not executed two times");
}
@Test
public void testFrequencySetting() {
TestUtils.ModelAndItem mai = createModelAndItem(0);
NumberItem numberItem = mai.item;
FrequencySetting itemPerformance = new FrequencySetting();
itemPerformance.setEventProcessingFrequency(10);
numberItem.setFrequencySetting(itemPerformance);
Rule rule = new Rule();
CountingAction counter = new CountingAction();
rule.addAction(counter);
rule.activateFor(numberItem);
numberItem.setState(1);
numberItem.setState(2);
assertEquals(1, counter.get(numberItem), "Action was triggered although FrequencySetting too small");
counter.reset();
waitMillis(100);
numberItem.setState(3);
assertEquals(1, counter.get(numberItem), "Action wasn't triggered although frequency FrequencySetting is small enough");
counter.reset();
numberItem.setState(4);
numberItem.setState(5);
assertEquals(0, counter.get(numberItem), "Action was triggered although FrequencySetting too small");
counter.reset();
}
private static void waitMillis(int millis) {
try {
Thread.sleep(millis);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment