Skip to content
Snippets Groups Projects
Commit 81794ffc authored by René Schöne's avatar René Schöne
Browse files

Merge branch '32-add-processing-frequency-option-for-items' into 'dev'

Resolve "Add processing frequency option for items"

See merge request !11
parents 93fbab4a 2479587d
Branches
No related tags found
2 merge requests!19dev to master,!11Resolve "Add processing frequency option for items"
Pipeline #9863 passed
......@@ -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