diff --git a/eraser-base/src/main/jastadd/Expression.relast b/eraser-base/src/main/jastadd/Expression.relast index 591e112a21b904c88a450e3a2cf92c982a6a4c03..3f09a660c0e0cc460cef2a0504245235443d7702 100644 --- a/eraser-base/src/main/jastadd/Expression.relast +++ b/eraser-base/src/main/jastadd/Expression.relast @@ -23,3 +23,5 @@ ComparingExpression : LogicalExpression ::= LeftOperand:NumberExpression RightOp abstract BinaryLogicalExpression : LogicalExpression ::= LeftOperand:LogicalExpression RightOperand:LogicalExpression ; AndExpression : BinaryLogicalExpression ; OrExpression : BinaryLogicalExpression ; + +ComparatorBox ::= <It:ComparatorType> ; diff --git a/eraser-base/src/main/jastadd/Printing.jrag b/eraser-base/src/main/jastadd/Printing.jrag index 60e77e3a00a122f60495df3f66216a08accfbdb2..b1761f17cdf003877f1f6b4ccb9b4314bfd51c8c 100644 --- a/eraser-base/src/main/jastadd/Printing.jrag +++ b/eraser-base/src/main/jastadd/Printing.jrag @@ -8,6 +8,9 @@ aspect Printing { eq Root.prettyPrint() { StringBuilder sb = new StringBuilder(); sb.append(getSmartHomeEntityModel().prettyPrint()); + for (Rule r : getRules()) { + sb.append(r.prettyPrint()); + } sb.append(getMqttRoot().prettyPrint()); sb.append(getInfluxRoot().prettyPrint()); sb.append(getMachineLearningRoot().prettyPrint()); @@ -66,6 +69,7 @@ aspect Printing { .addOptional("category", hasCategory(), () -> getCategory().getName()) .addOptional("topic", hasTopic(), () -> getTopic().getTopicString()) .addOptionalPrettyPrint(getMetaData()) + .addOptional("performance", hasFrequencySetting(), () -> getFrequencySetting().getID()) .build(); } @@ -241,30 +245,92 @@ aspect Printing { syn String ParenthesizedLogicalExpression.prettyPrint() = "(" + getOperand().prettyPrint() + ")"; syn String NotExpression.prettyPrint() = "!" + getOperand().prettyPrint(); eq ComparingExpression.prettyPrint() { - switch (getComparator()) { - case NotEquals: return "(" + getLeftOperand().prettyPrint() + " != " + getRightOperand().prettyPrint() + ")"; - case Equals: return "(" + getLeftOperand().prettyPrint() + " == " + getRightOperand().prettyPrint() + ")"; - case LessThan: return "(" + getLeftOperand().prettyPrint() + " < " + getRightOperand().prettyPrint() + ")"; - case GreaterThan: return "(" + getLeftOperand().prettyPrint() + " > " + getRightOperand().prettyPrint() + ")"; - case LessOrEqualThan: return "(" + getLeftOperand().prettyPrint() + " <= " + getRightOperand().prettyPrint() + ")"; - case GreaterOrEqualThan: return "(" + getLeftOperand().prettyPrint() + " >= " + getRightOperand().prettyPrint() + ")"; - default: throw new IllegalArgumentException("Unknown compartor type: " + getComparator()); - } + return "(" + getLeftOperand().prettyPrint() + Condition.operatorAsString(getComparator()) + getRightOperand().prettyPrint() + ")"; } syn String AndExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " & " + getRightOperand().prettyPrint() + ")"; syn String OrExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " | " + getRightOperand().prettyPrint() + ")"; syn String Designator.prettyPrint() = getItem().getID(); + + public static String Condition.operatorAsString(ComparatorType comparatorType) { + switch (comparatorType) { + case NotEquals: return "!="; + case Equals: return "=="; + case LessThan: return "<"; + case GreaterThan: return ">"; + case LessOrEqualThan: return "<="; + case GreaterOrEqualThan: return ">="; + default: throw new IllegalArgumentException("Unknown comparator type: " + comparatorType); + } + } // Rules eq Rule.prettyPrint() { return new MemberPrinter("Rule") - .addIds("TriggeringItems", getObserverList().size(), getObserverList(), - io -> io.observedItem().getID()) - .addNodes("Condition", getNumCondition(), getConditionList(), - Condition::toString) - .addNodes("Action", getNumAction(), getActionList(), - Action::toString) + .addRequired("name", getID()) + .addCustomIterable("items", getObserverList().size(), getObserverList(), + io -> io.observedItem().getID(),true) + .addIterable(new JastAddList<>().addAll(getConditionList()).addAll(getActionList())) .build(); } + //ItemStateCheck + eq ItemStateCheckCondition.prettyPrint() { + return "Condition: ItemStateCheck " + getItemStateCheck().prettyPrint(); + } + + //ItemStateNumberCheck + eq ItemStateNumberCheck.prettyPrint() { + return Condition.operatorAsString(getComparator()) + " " + getValue(); + } + + //ItemStateStringCheck + eq ItemStateStringCheck.prettyPrint() { + return Condition.operatorAsString(getComparator()) + " " + "\""+getValue()+"\""; + } + + //ItemStateChangeCondition + eq ItemStateChangeCondition.prettyPrint() { + return "Condition: ItemStateChange " + getItem().getID(); + } + + //ExpressionCondition + eq ExpressionCondition.prettyPrint() { + return "Condition: Expression " + getLogicalExpression().prettyPrint(); + } + + + //TriggerRuleAction + eq TriggerRuleAction.prettyPrint() { + return "Action: TriggerRule " + getRule().getID(); + } + + //NoopAction + eq NoopAction.prettyPrint() { + return "Action: Noop"; + } + + //SetStateFromConstantStringAction + eq SetStateFromConstantStringAction.prettyPrint() { + return "Action: SetStateFromConstant " + getAffectedItem().getID() + " \"" + getNewState()+ "\""; + } + + //SetStateFromTriggeringItemAction + eq SetStateFromTriggeringItemAction.prettyPrint() { + return "Action: SetStateFromTrigger " + getAffectedItem().getID(); + } + + //MultiplyDoubleToStateAction + eq MultiplyDoubleToStateAction.prettyPrint() { + return "Action: SetStateFromTrigger " + getAffectedItem().getID() + " " + getMultiplier(); + } + + //SetStateFromExpressionAction + eq SetStateFromExpressionAction.prettyPrint() { + return "Action: StateFromExpression " + getNumberExpression().prettyPrint() + " " + getAffectedItem().getID(); + } + + + + + } diff --git a/eraser-base/src/main/jastadd/Resolving.jrag b/eraser-base/src/main/jastadd/Resolving.jrag index 7d6656121956a1c3014d1fed3dbc14832b1556d1..5a1a8ca939e32054970efc9b032d305ba43798ab 100644 --- a/eraser-base/src/main/jastadd/Resolving.jrag +++ b/eraser-base/src/main/jastadd/Resolving.jrag @@ -88,6 +88,16 @@ aspect Resolving { return java.util.Optional.empty(); } + //--- resolveRule --- + syn java.util.Optional<Rule> Root.resolveRule(String ruleName) { + for (Rule rule : getRules()) { + if (rule.getID().equals(ruleName)) { + return java.util.Optional.of(rule); + } + } + return java.util.Optional.empty(); + } + //--- resolveActivity --- syn java.util.Optional<Activity> Root.resolveActivity(int identifier) { for (Activity activity : getMachineLearningRoot().getActivityList()) { @@ -136,6 +146,11 @@ aspect Resolving { return getRoot().getSmartHomeEntityModel().resolveFrequencySetting(id).orElseThrow(() -> new RuntimeException("FrequencySetting '" + id + "' not found!")); } + // _._ -> Rule + refine RefResolverStubs eq ASTNode.globallyResolveRuleByToken(String id) { + return getRoot().resolveRule(id).orElseThrow(() -> new RuntimeException("Rule '" + id + "' not found!")); + } + // Thing.Channel* -> Channel refine RefResolverStubs eq Thing.resolveChannelByToken(String id, int position) { return containingSmartHomeEntityModel().resolveChannel(id).orElseThrow(() -> new RuntimeException("Channel '" + id + "' not found!")); diff --git a/eraser-base/src/main/jastadd/Rules.jrag b/eraser-base/src/main/jastadd/Rules.jrag index e2acfca41c585dedfc71bd56c0772abcef36d247..1036024413db67e7b6d7adb0fe4d405868d8442f 100644 --- a/eraser-base/src/main/jastadd/Rules.jrag +++ b/eraser-base/src/main/jastadd/Rules.jrag @@ -76,21 +76,16 @@ aspect Rules { public void NoopAction.applyFor(Item item) { // empty by design } - public void LambdaAction.applyFor(Item item) { - getLambda().accept(item); - } + public void TriggerRuleAction.applyFor(Item item) { getRule().trigger(item); } - public void SetStateFromExpression.applyFor(Item item) { + public void SetStateFromExpressionAction.applyFor(Item item) { getAffectedItem().setStateFromDouble(getNumberExpression().eval()); } public void SetStateFromConstantStringAction.applyFor(Item item) { getAffectedItem().setStateFromString(getNewState()); } - public void SetStateFromLambdaAction.applyFor(Item item) { - getAffectedItem().setStateFromString(getNewStateProvider().get()); - } public void SetStateFromTriggeringItemAction.applyFor(Item item) { Item target = getAffectedItem(); if (target==item) { @@ -98,9 +93,6 @@ aspect Rules { } item.copyStateTo(target); } - public void SetStateFromItemsAction.applyFor(Item item) { - getAffectedItem().setStateFromString(getCombinator().apply(getSourceItems())); - } public void AddDoubleToStateAction.applyFor(Item item) { getAffectedItem().setStateFromDouble(getAffectedItem().getStateAsDouble() + getIncrement()); } @@ -116,7 +108,7 @@ aspect StateSyncGroup { Rule rule = new Rule(); for (Item item : getTargetItemList()) { - rule.addAction(new SetStateFromTriggeringItemAction(item)); + rule.addAction(new SetStateFromTriggeringItemAction().setAffectedItem(item)); rule.addObserver(item.getItemObserver()); } diff --git a/eraser-base/src/main/jastadd/Rules.relast b/eraser-base/src/main/jastadd/Rules.relast index e6c3ab0138742149407d3a6279a958c2cc551ee4..b78d52a079f206a31064f1f98c03672a51c666c8 100644 --- a/eraser-base/src/main/jastadd/Rules.relast +++ b/eraser-base/src/main/jastadd/Rules.relast @@ -1,5 +1,5 @@ // --- New ECA rules --- -Rule ::= Condition* Action* ; +Rule : ModelElement ::= Condition* Action* ; abstract Condition ; ItemStateCheckCondition : Condition ::= ItemStateCheck ; ItemStateChangeCondition : Condition ; @@ -7,7 +7,6 @@ rel ItemStateChangeCondition.Item -> Item; ExpressionCondition : Condition ::= LogicalExpression ; abstract Action ; NoopAction : Action ; -LambdaAction : Action ::= <Lambda:Action2EditConsumer> ; TriggerRuleAction : Action ; rel TriggerRuleAction.Rule -> Rule ; @@ -15,14 +14,11 @@ rel TriggerRuleAction.Rule -> Rule ; abstract SetStateAction : Action ; rel SetStateAction.AffectedItem -> Item ; -SetStateFromExpression : SetStateAction ::= NumberExpression ; +SetStateFromExpressionAction : SetStateAction ::= NumberExpression ; SetStateFromConstantStringAction : SetStateAction ::= <NewState:String> ; -SetStateFromLambdaAction : SetStateAction ::= <NewStateProvider:NewStateProvider> ; SetStateFromTriggeringItemAction : SetStateAction ::= ; -SetStateFromItemsAction : SetStateAction ::= <Combinator:ItemsToStringFunction> ; -rel SetStateFromItemsAction.SourceItem* -> Item ; AddDoubleToStateAction : SetStateAction ::= <Increment:double> ; MultiplyDoubleToStateAction : SetStateAction ::= <Multiplier:double> ; @@ -33,3 +29,7 @@ rel ItemObserver.TriggeredRule* <-> Rule.Observer* ; FrequencySetting : LabelledModelElement ::= <EventProcessingFrequency:double> ; StateSyncGroup : Rule ; rel StateSyncGroup.TargetItem* -> Item; + +ConditionActionPack ::= ; +rel ConditionActionPack.Condition* -> Condition; +rel ConditionActionPack.Action* -> Action; \ No newline at end of file diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex index 54dff35b310e573c8efe700596f57b36b276235a..c1c6b52ebbc7c591f22b87d384e0d20f1026dcbe 100644 --- a/eraser-base/src/main/jastadd/eraser.flex +++ b/eraser-base/src/main/jastadd/eraser.flex @@ -71,12 +71,23 @@ Comment = "//" [^\n\r]+ "RollerShutter" { return sym(Terminals.ROLLER_SHUTTER); } "String" { return sym(Terminals.STRING); } "Switch" { return sym(Terminals.SWITCH); } +// actions and conditions +"ItemStateCheck" { return sym(Terminals.ITEM_STATE_CHECK); } +"ItemStateChange" { return sym(Terminals.ITEM_STATE_CHANGE); } +"Expression" { return sym(Terminals.EXPRESSION); } +"Noop" { return sym(Terminals.NOOP_ACTION); } +"TriggerRule" { return sym(Terminals.TRIGGER_RULE_ACTION); } +"SetStateFromConstant" { return sym(Terminals.STATE_FROM_CONST_ACTION); } +"SetStateFromTrigger" { return sym(Terminals.STATE_FROM_TRIGGER_ACTION); } +"StateFromExpression" { return sym(Terminals.STATE_FROM_EXPRESSION_ACTION); } // within specification +"Action" { return sym(Terminals.ACTION); } "activities" { return sym(Terminals.ACTIVITIES); } "aggregation" { return sym(Terminals.AGGREGATION); } "category" { return sym(Terminals.CATEGORY); } "channels" { return sym(Terminals.CHANNELS); } "channelTypes" { return sym(Terminals.CHANNEL_TYPES); } +"Condition" { return sym(Terminals.CONDITION); } "context" { return sym(Terminals.CONTEXT); } "dbName" { return sym(Terminals.DB_NAME); } "default" { return sym(Terminals.DEFAULT); } @@ -84,6 +95,7 @@ Comment = "//" [^\n\r]+ "groups" { return sym(Terminals.GROUPS); } "host" { return sym(Terminals.HOST); } "id" { return sym(Terminals.ID); } +"name" { return sym(Terminals.LABELNAME); } "incoming" { return sym(Terminals.INCOMING); } "items" { return sym(Terminals.ITEMS); } "itemType" { return sym(Terminals.ITEM_TYPE); } diff --git a/eraser-base/src/main/jastadd/eraser.parser b/eraser-base/src/main/jastadd/eraser.parser index 0da0601d2136e5ed105eddd81751da9d38e848c8..456027b12235de42421b539218f7d82fca904871 100644 --- a/eraser-base/src/main/jastadd/eraser.parser +++ b/eraser-base/src/main/jastadd/eraser.parser @@ -95,7 +95,7 @@ import java.util.HashMap; rule.addCondition(condition); break; case AltGoals.number_expression: - SetStateFromExpression action = new SetStateFromExpression(); + SetStateFromExpressionAction action = new SetStateFromExpressionAction(); action.setNumberExpression(exp.asNumberExpression()); rule.addAction(action); break; @@ -127,7 +127,7 @@ Root root = | influx_root.ir root.r {: r.setInfluxRoot(ir); return r; :} | machine_learning_root.ml root.r {: r.setMachineLearningRoot(ml); return r; :} | rule.rule root.r {: r.addRule(rule); return r; :} - | frequency_setting.ip root.r {: insertZero(r.getSmartHomeEntityModel().getFrequencySettingList(), ip); return r; :} + | frequency_setting.fs root.r {: insertZero(r.getSmartHomeEntityModel().getFrequencySettingList(), fs); return r; :} | {: return Root.createEmptyRoot(); :} ; @@ -157,12 +157,7 @@ LogicalExpression logical_expression = | LB_ROUND logical_expression.a OR logical_expression.b RB_ROUND {: return new OrExpression(a, b); :} | EXCLAMATION logical_expression.e {: return new NotExpression(e); :} | LB_ROUND logical_expression.e RB_ROUND {: return new ParenthesizedLogicalExpression(e); :} - | LB_ROUND number_expression.a LT number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.LessThan); :} - | LB_ROUND number_expression.a LE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.LessOrEqualThan); :} - | LB_ROUND number_expression.a EQ number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.Equals); :} - | LB_ROUND number_expression.a NE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.NotEquals); :} - | LB_ROUND number_expression.a GE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.GreaterOrEqualThan); :} - | LB_ROUND number_expression.a GT number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.GreaterThan); :} + | LB_ROUND number_expression.a comparator.cp number_expression.b RB_ROUND {: return new ComparingExpression(a, b, cp.getIt()); :} ; NumberLiteralExpression literal_expression = @@ -372,21 +367,71 @@ MachineLearningRoot machine_learning_root_body = // Rule: condition=condition action=a1 action=a2; // (only allow one condition and action for now) +//Rule rule = +// RULE COLON CONDITION EQUALS condition.c action.a SEMICOLON {: Rule result = new Rule(); +// result.addCondition(c); +// result.addAction(a); +// return result; :} +// ; + + + Rule rule = - RULE COLON CONDITION EQUALS condition.c action.a SEMICOLON {: Rule result = new Rule(); - result.addCondition(c); - result.addAction(a); - return result; :} + RULE COLON rule_body.rb SEMICOLON {: return rb; :} ; -Condition condition = - logical_expression.be {: return new ExpressionCondition(be); :} + +//Rule: name="testRue1" items=["ITEM_ID","ITEM_ID"] conditions=["CONDITION", "CONDITION"] action=["ACTION", "ACTION"] +// (only allow one condition and action for now) +Rule rule_body = + LABELNAME EQUALS TEXT.n rule_body.r {: return r.setID(n); :} + | ITEMS EQUALS string_list.items rule_body.r {: items.forEach(i -> r.activateFor(createItemPlaceHolder(i))); return r; :} + | LB_SQUARE capack.cap RB_SQUARE rule_body.r {: return r.setActionList(new JastAddList().addAll(cap.getActions())).setConditionList(new JastAddList().addAll(cap.getConditions())); :} + | {: return new Rule(); :} ; -// TODO implement action cases -Action action = {: return new NoopAction(); :} + +ConditionActionPack capack = + COMMA capack.cap {: return cap; :} + | CONDITION COLON condition.c capack.cap {: cap.addCondition(c); return cap; :} + | ACTION COLON action.a capack.cap {: cap.addAction(a); return cap; :} + | {: return new ConditionActionPack(); :} + ; + +Condition condition = + ITEM_STATE_CHECK isccondition.iscc {: return iscc; :} + | ITEM_STATE_CHANGE NAME.n {: return new ItemStateChangeCondition(Item.createRef(n)); :} + | EXPRESSION logical_expression.le {: return new ExpressionCondition(le); :} + ; +ItemStateCheckCondition isccondition = + comparator.cp REAL.r {: return new ItemStateCheckCondition().setItemStateCheck(new ItemStateNumberCheck().setValue(Double.parseDouble(r)).setComparator(cp.getIt())); :} + | comparator.cp TEXT.n {: return new ItemStateCheckCondition().setItemStateCheck(new ItemStateStringCheck().setValue(n).setComparator(cp.getIt())); :} + +ComparatorBox comparator = + NE {: return new ComparatorBox(ComparatorType.NotEquals); :} + | EQ {: return new ComparatorBox(ComparatorType.Equals); :} + | LT {: return new ComparatorBox(ComparatorType.LessThan); :} + | GT {: return new ComparatorBox(ComparatorType.GreaterThan); :} + | LE {: return new ComparatorBox(ComparatorType.LessOrEqualThan); :} + | GE {: return new ComparatorBox(ComparatorType.GreaterOrEqualThan); :} + ; + + + +Action action = + TRIGGER_RULE_ACTION NAME.n {: return new TriggerRuleAction().setRule(Rule.createRef(n)); :} + | NOOP_ACTION {:return new NoopAction(); :} + | STATE_FROM_CONST_ACTION NAME.i TEXT.s {: return new SetStateFromConstantStringAction().setNewState(s).setAffectedItem(Item.createRef(i)); :} + | STATE_FROM_TRIGGER_ACTION NAME.i {: return new SetStateFromTriggeringItemAction().setAffectedItem(Item.createRef(i)); :} + | STATE_FROM_TRIGGER_ACTION NAME.i REAL.r {: return new MultiplyDoubleToStateAction().setMultiplier(Double.parseDouble(r)).setAffectedItem(Item.createRef(i)); :} + | STATE_FROM_EXPRESSION_ACTION number_expression.ne NAME.i {: return new SetStateFromExpressionAction().setNumberExpression(ne).setAffectedItem(Item.createRef(i)); :} + ; + + + + // Util: StringList, StringKeyMap, IntegerKeyMap StringList string_list = LB_SQUARE string_list_body.slb RB_SQUARE {: return slb; :} diff --git a/eraser-base/src/main/jastadd/shem.jrag b/eraser-base/src/main/jastadd/shem.jrag index 1eb0dfa753a45b36fe7047e662a6577b8b27eea5..bed14719588736d3c819f94d5886a2cdd0f17ae7 100644 --- a/eraser-base/src/main/jastadd/shem.jrag +++ b/eraser-base/src/main/jastadd/shem.jrag @@ -28,6 +28,7 @@ aspect SmartHomeEntityModel { result.setID(this.getID()); result.setLabel(this.getLabel()); result.setMetaData(this.getMetaData()); + result.setFrequencySetting(this.getFrequencySetting()); if (this.hasTopic()) { result.setTopic(this.getTopic()); } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java index 53acf15253fcfc3a91067ca332ea8e2f2cdf7642..da6b04f969d8f48e7eabb187ea83798e0a1d5ef6 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java @@ -5,9 +5,13 @@ import de.tudresden.inf.st.eraser.jastadd.model.ModelElement; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * Fluent API to pretty-print ASTNodes in a line-based fashion. @@ -91,7 +95,7 @@ public class MemberPrinter { * @param listOfNodes the list of nodes * @param mapping a function to map a node to a String */ - private <T extends ASTNode<?>> void concatNodes(Iterable<T> listOfNodes, + private <T> void concatNodes(Iterable<T> listOfNodes, Function<T, String> mapping, boolean quote) { boolean first = true; @@ -129,9 +133,25 @@ public class MemberPrinter { * @return this */ public MemberPrinter addIds(String name, int count, Iterable<? extends ModelElement> listOfElements) { + return addCustomIterable(name,count,listOfElements,ModelElement::getID,true); + } + + /** + * Appends model elements to the builder, if count is positive. + * @param name The name of the member + * @param count The number of items in the listOfElements + * @param listOfElements The list of model elements + * @param printPart The function that is called on the elements of the list + * @return this + */ + public <T> MemberPrinter addCustomIterable(String name, int count, Iterable<T> listOfElements, Function<T,String> printPart,boolean quote) { if (count > 0) { - sb.append(' ').append(name).append("=["); - concatIds(listOfElements); + sb.append(' '); + if (name!=null && !name.isEmpty()) { + sb.append(name).append("="); + } + sb.append("["); + concatNodes(listOfElements,printPart,quote); sb.append("]"); this.empty = false; } @@ -139,13 +159,37 @@ public class MemberPrinter { } /** - * Appends nodes to the builder, if count is positive. - * @param name The name of the member - * @param count The number of items in the listOfNodes - * @param listOfNodes The list of nodes - * @param mapping A function to map a node to a ModelElement + * Appends model elements to the builder, if count is positive. "Name=..." won't appear + * @param count The number of items in the listOfElements + * @param listOfElements The list of model elements + * @param printPart The function that is called on the elements of the list * @return this */ + public <T> MemberPrinter addCustomIterable(int count, Iterable<T> listOfElements, Function<T,String> printPart,boolean quote) { + return addCustomIterable(null,count,listOfElements,printPart,quote); + } + public <T> MemberPrinter addCustomIterable(String name, Iterable<T> listOfElements, Function<T,String> printPart,boolean quote) { + List<T> list = StreamSupport.stream(listOfElements.spliterator(), false) + .collect(Collectors.toList()); + return addCustomIterable(name,list.size(),list,printPart,quote); + } + public <T extends ASTNode<?>> MemberPrinter addIterable(Iterable<T> listOfElements) { + return addIterable(null,listOfElements); + } + + public <T extends ASTNode<?>> MemberPrinter addIterable(String name,Iterable<T> listOfElements) { + return addCustomIterable(name,listOfElements,ASTNode::prettyPrint,false); + } + + + /** + * Appends nodes to the builder, if count is positive. + * @param name The name of the member + * @param count The number of items in the listOfNodes + * @param listOfNodes The list of nodes + * @param mapping A function to map a node to a ModelElement + * @return this + */ public <T extends ASTNode<?>> MemberPrinter addIds( String name, int count, Iterable<T> listOfNodes, Function<T, String> mapping) { @@ -254,7 +298,7 @@ public class MemberPrinter { * @return this */ public MemberPrinter addNonDefault(String name, Object actualValue, Object defaultValue) { - if (!actualValue.equals(defaultValue)) { + if (actualValue==null || !actualValue.equals(defaultValue)) { this.empty = false; return add(name, actualValue); } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java index a8c1f727a599a1f529173428dee1d11fa5974156..fa91036013bab2f16cf0e4c1428b1b5c7e318e60 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java @@ -225,8 +225,8 @@ public class RulesTest { Rule rule = new Rule(); ItemStateNumberCheck check1 = new ItemStateNumberCheck(ComparatorType.GreaterOrEqualThan, 4); ItemStateNumberCheck check2 = new ItemStateNumberCheck(ComparatorType.LessThan, 6); - rule.addCondition(new ItemStateCheckCondition(check1)); - rule.addCondition(new ItemStateCheckCondition(check2)); + rule.addCondition(new ItemStateCheckCondition().setItemStateCheck(check1)); + rule.addCondition(new ItemStateCheckCondition().setItemStateCheck(check2)); CountingAction counter = new CountingAction(); rule.addAction(counter); root.addRule(rule); @@ -540,7 +540,7 @@ public class RulesTest { CountingAction counter2 = new CountingAction(); ruleB.addAction(counter2); - ruleA.addAction(new TriggerRuleAction(ruleB)); + ruleA.addAction(new TriggerRuleAction().setRule(ruleB)); root.addRule(ruleA); root.addRule(ruleB); @@ -576,7 +576,7 @@ public class RulesTest { NumberItem item2 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 4, true); Rule rule = new Rule(); - rule.addAction(new SetStateFromConstantStringAction(item2, "5")); + rule.addAction(new SetStateFromConstantStringAction().setNewState("5").setAffectedItem(item2)); CountingAction counter = new CountingAction(); rule.addAction(counter); root.addRule(rule); @@ -597,44 +597,6 @@ public class RulesTest { } } - @Test - public void testSetStateFromLambdaAction() { - ValuedStateProvider provider = new ValuedStateProvider(); - TestUtils.ModelAndItem modelAndItem = createModelAndItem(0); - Root root = modelAndItem.model.getRoot(); - NumberItem item = modelAndItem.item; - NumberItem item2 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 3, true); - - Rule rule = new Rule(); - rule.addAction(new SetStateFromLambdaAction(item2, provider)); - CountingAction counter = new CountingAction(); - rule.addAction(counter); - root.addRule(rule); - rule.activateFor(item); - - assertEquals(3, item2.asItemWithDoubleState().getState(), DELTA, "Affected item not initialized correctly"); - - provider.value = 4; - setState(item, 1); - assertEquals(1, counter.get(item), "Change of item state should trigger the rule"); - assertEquals(4, item2.asItemWithDoubleState().getState(), DELTA, "Change of item state should set the state of the affected item"); - - provider.value = 4; - setState(item, 2); - assertEquals(2, counter.get(item), "Change of item state should trigger the rule"); - assertEquals(4, item2.asItemWithDoubleState().getState(), DELTA, "Change of item state should set the state of the affected item"); - - provider.value = 5; - setState(item, 2); - assertEquals(2, counter.get(item), "Change of item to same state should not trigger the rule"); - assertEquals(4, item2.asItemWithDoubleState().getState(), DELTA, "Change of item to same state should not set the state of the affected item"); - - provider.value = 5; - setState(item, 3); - assertEquals(3, counter.get(item), "Change of item state should trigger the rule"); - assertEquals(5, item2.asItemWithDoubleState().getState(), DELTA, "Change of item state should set the state of the affected item"); - } - @Test public void testSetStateFromTriggeringItemAction() { TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); @@ -644,7 +606,7 @@ public class RulesTest { Rule rule = new Rule(); CountingAction counter = new CountingAction(); - rule.addAction(new SetStateFromTriggeringItemAction(item2)); + rule.addAction(new SetStateFromTriggeringItemAction().setAffectedItem(item2)); rule.addAction(counter); root.addRule(rule); rule.activateFor(item); @@ -665,65 +627,6 @@ public class RulesTest { assertEquals("7.0", item2.getState(), "Change of item state should set the state of the affected item"); } - @Test - public void testSetStateFromItemsAction() { - TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); - Root root = modelAndItem.model.getRoot(); - NumberItem item = modelAndItem.item; - NumberItem item2 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 4, true); - StringItem affectedItem = addStringItem(root.getSmartHomeEntityModel(), "1"); - - Rule rule = new Rule(); - SetStateFromItemsAction action = new SetStateFromItemsAction(items -> - Long.toString(StreamSupport.stream(items.spliterator(), false) - .mapToLong(inner -> (long) inner.asItemWithDoubleState().getState()) - .sum())); - action.addSourceItem(item); - action.addSourceItem(item2); - action.setAffectedItem(affectedItem); - rule.addAction(action); - CountingAction counter = new CountingAction(); - rule.addAction(counter); - root.addRule(rule); - rule.activateFor(item); - - assertEquals(0, counter.get(item), "Counter not initialized correctly"); - assertEquals("1", affectedItem.getState(), "Affected item not initialized correctly"); - - // 5 + 4 = 9 - setState(item, 5); - assertEquals(1, counter.get(item), "Change of item state should trigger the rule"); - assertEquals("9", affectedItem.getState(), "Change of item state should set the state of the affected item"); - - // still 5 + 4 = 9, as rule does not trigger for item2 - setState(item2, 5); - assertEquals(1, counter.get(item), "Change of item2 state should not trigger the rule"); - assertEquals("9", affectedItem.getState(), "Change of item2 state should not set the state of the affected item"); - - // still 5 + 4 = 9, as rule should not trigger - setState(item, 5); - assertEquals(1, counter.get(item), "Change of item to same state should not trigger the rule"); - assertEquals("9", affectedItem.getState(), "Change of item to same state should not set the state of the affected item"); - - // 7 + 5 = 12 - setState(item, 7); - assertEquals(2, counter.get(item), "Change of item state should trigger the rule"); - assertEquals("12", affectedItem.getState(), "Change of item state should set the state of the affected item"); - - // add new item to sum - NumberItem item3 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), -4, true); - action.addSourceItem(item3); - - // still 7 + 5 = 12, as rule should not trigger - setState(item, 7); - assertEquals(2, counter.get(item), "Change of item to same state should not trigger the rule"); - assertEquals("12", affectedItem.getState(), "Change of item to same state should not set the state of the affected item"); - - // 8 + 5 - 4 = 9 - setState(item, 8); - assertEquals(3, counter.get(item), "Change of item state should trigger the rule"); - assertEquals("9", affectedItem.getState(), "Change of item state should set the state of the affected item"); - } @Test public void testAddDoubleToStateAction() { @@ -733,7 +636,7 @@ public class RulesTest { NumberItem affectedItem = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 4, true); Rule rule = new Rule(); - rule.addAction(new AddDoubleToStateAction(affectedItem, 2)); + rule.addAction(new AddDoubleToStateAction().setIncrement(2).setAffectedItem(affectedItem)); CountingAction counter = new CountingAction(); rule.addAction(counter); root.addRule(rule); @@ -766,7 +669,7 @@ public class RulesTest { NumberItem affectedItem = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 4, true); Rule rule = new Rule(); - rule.addAction(new MultiplyDoubleToStateAction(affectedItem, 2)); + rule.addAction(new MultiplyDoubleToStateAction().setMultiplier(2).setAffectedItem(affectedItem)); CountingAction counter = new CountingAction(); rule.addAction(counter); root.addRule(rule); @@ -800,16 +703,16 @@ public class RulesTest { NumberItem affectedItem = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 5, true); Rule ruleA = new Rule(); - ruleA.addAction(new AddDoubleToStateAction(affectedItem, 2)); + ruleA.addAction(new AddDoubleToStateAction().setIncrement(2).setAffectedItem(affectedItem)); CountingAction counterA = new CountingAction(); ruleA.addAction(counterA); Rule ruleB = new Rule(); - ruleB.addAction(new MultiplyDoubleToStateAction(affectedItem, 3)); + ruleB.addAction(new MultiplyDoubleToStateAction().setMultiplier(3).setAffectedItem(affectedItem)); CountingAction counterB = new CountingAction(); ruleB.addAction(counterB); - ruleA.addAction(new TriggerRuleAction(ruleB)); + ruleA.addAction(new TriggerRuleAction().setRule(ruleB)); root.addRule(ruleA); root.addRule(ruleB); @@ -851,7 +754,7 @@ public class RulesTest { NumberItem affectedItem = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 5, true); Rule rule = new Rule(); - SetStateFromExpression action = new SetStateFromExpression(); + SetStateFromExpressionAction action = new SetStateFromExpressionAction(); // TODO item1 should be referred to as triggering item action.setNumberExpression(ParserUtils.parseNumberExpression("(" + item1.getID() + " + " + item2.getID() + ")", root)); action.setAffectedItem(affectedItem); diff --git a/eraser-base/src/test/resources/tests/ppc2/input.eraser b/eraser-base/src/test/resources/tests/ppc2/input.eraser index 36fddab19dc562bbed3fe437c147016f64ef7eee..a2793872f590861be7a06194c8b1f1e0de424d0c 100644 --- a/eraser-base/src/test/resources/tests/ppc2/input.eraser +++ b/eraser-base/src/test/resources/tests/ppc2/input.eraser @@ -1,5 +1,5 @@ Mqtt: incoming="ppc2/" outgoing="oh2/in" host="localhost" ; -Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state"; +Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" performance="ip1" ; Group: id="my-group" items=["iris1_item"] ; ThingType: id="skywriter-hat" label="SkyWriterHAT" description="SkyWriterHAT Gesture Recognition" parameters=["brokername"] channelTypes=["flick-type"] ; ChannelType: id="flick-type" itemType="String" label="Last Flick" description="Last Flick detected (and its direction)" category="Motion" readOnly ; diff --git a/eraser-base/src/test/resources/tests/ppc2/output.eraser b/eraser-base/src/test/resources/tests/ppc2/output.eraser index 9fec0c1fd2611dd05a1e7872899aecd21936ff89..30855cfc04eb3dab0d42e419cb159959550d37e3 100644 --- a/eraser-base/src/test/resources/tests/ppc2/output.eraser +++ b/eraser-base/src/test/resources/tests/ppc2/output.eraser @@ -1,4 +1,4 @@ -Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" ; +Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" performance="ip1" ; Group: id="my-group" items=["iris1_item"] ; ThingType: id="skywriter-hat" label="SkyWriterHAT" description="SkyWriterHAT Gesture Recognition" parameters=["brokername"] channelTypes=["flick-type"] ; ChannelType: id="flick-type" label="Last Flick" description="Last Flick detected (and its direction)" itemType="String" category="Motion" readOnly ; diff --git a/eraser-base/src/test/resources/tests/ppc6/Test.properties b/eraser-base/src/test/resources/tests/ppc6/Test.properties new file mode 100644 index 0000000000000000000000000000000000000000..616a3deddd4decffd5d0e6e3db7bb8ce3873562a --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc6/Test.properties @@ -0,0 +1 @@ +result=OUTPUT_PASS diff --git a/eraser-base/src/test/resources/tests/ppc6/description b/eraser-base/src/test/resources/tests/ppc6/description new file mode 100644 index 0000000000000000000000000000000000000000..4546caf78b69eb510a37a3856ebebcbfc0da3f15 --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc6/description @@ -0,0 +1 @@ +Test of different actions and conditions in rules \ No newline at end of file diff --git a/eraser-base/src/test/resources/tests/ppc6/input.eraser b/eraser-base/src/test/resources/tests/ppc6/input.eraser new file mode 100644 index 0000000000000000000000000000000000000000..4debf7eb64563c1b54687e118e9c629126307e22 --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc6/input.eraser @@ -0,0 +1,15 @@ +Color Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" ; +Color Item: id="iris2_item" label="Iris 2" state="121,88,68" topic="iris1_item/state" ; +Group: id="Unknown" items=["iris1_item", "iris2_item"] ; +Rule: name="testRule2" items=["iris1_item"] [Condition: ItemStateCheck < 6.0] ; +Rule: name="eqfff" items=["iris1_item"] [Condition: ItemStateCheck < "Yeah" ] ; +Rule: name="testRule1" items=["iris1_item"] [Condition: ItemStateCheck < 4.0, Action: TriggerRule testRule2] ; +Rule: name="testRule416" items=["iris1_item"] [Condition: ItemStateChange iris1_item] ; +Rule: name="testRule456" items=["iris1_item"] [Action: Noop] ; +Rule: name="testRule46" items=["iris1_item"] [Action: SetStateFromConstant iris1_item "K"] ; +Rule: name="testRule8" items=["iris1_item"] [Action: SetStateFromTrigger iris2_item] ; +Rule: name="testRul118" items=["iris1_item"] [Action: SetStateFromTrigger iris2_item 2.0] ; +Rule: name="testR8" items=["iris1_item"] [Condition: Expression (2.0<3.0)] ; +Rule: name="testR8" items=["iris1_item"] [Condition: Expression (2.0<iris1_item)] ; +Rule: name="testR8556" items=["iris1_item"] [Action: StateFromExpression (2.0 + 3.0) iris1_item] ; +Rule: name="testR8556" items=["iris1_item"] [Action: StateFromExpression iris1_item iris1_item] ; diff --git a/eraser-base/src/test/resources/tests/ppc6/output.eraser b/eraser-base/src/test/resources/tests/ppc6/output.eraser new file mode 100644 index 0000000000000000000000000000000000000000..ab7f5ee68c4dbf169d6538756744c8aac741a87f --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc6/output.eraser @@ -0,0 +1,15 @@ +Color Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" ; +Color Item: id="iris2_item" label="Iris 2" state="121,88,68" topic="iris1_item/state" ; +Group: id="Unknown" items=["iris1_item", "iris2_item"] ; +Rule: name="testR8556" items=["iris1_item"] [Action: StateFromExpression iris1_item iris1_item] ; +Rule: name="testR8556" items=["iris1_item"] [Action: StateFromExpression (2.0 + 3.0) iris1_item] ; +Rule: name="testR8" items=["iris1_item"] [Condition: Expression (2.0<iris1_item)] ; +Rule: name="testR8" items=["iris1_item"] [Condition: Expression (2.0<3.0)] ; +Rule: name="testRul118" items=["iris1_item"] [Action: SetStateFromTrigger iris2_item 2.0] ; +Rule: name="testRule8" items=["iris1_item"] [Action: SetStateFromTrigger iris2_item] ; +Rule: name="testRule46" items=["iris1_item"] [Action: SetStateFromConstant iris1_item "K"] ; +Rule: name="testRule456" items=["iris1_item"] [Action: Noop] ; +Rule: name="testRule416" items=["iris1_item"] [Condition: ItemStateChange iris1_item] ; +Rule: name="testRule1" items=["iris1_item"] [Condition: ItemStateCheck < 4.0, Action: TriggerRule testRule2] ; +Rule: name="eqfff" items=["iris1_item"] [Condition: ItemStateCheck < "Yeah"] ; +Rule: name="testRule2" items=["iris1_item"] [Condition: ItemStateCheck < 6.0] ; diff --git a/pages/docs/DSL.md b/pages/docs/DSL.md index 7ac5c9f9b6c5dde3768c40a68e804be9fa5c6d50..b8f12e6a6c6dde69012955204ee63556cdb38275 100644 --- a/pages/docs/DSL.md +++ b/pages/docs/DSL.md @@ -98,3 +98,40 @@ Influx: user="" password="" dbName="" host="" ; - `user` and `password` define connection credentials - `dbName` defines the name of the database to use - `host` defines the URL of the host running an InfluxDB. The port will be set to `8086` and cannot be changed. + + +## Rule + +``` +Rule: name="" items=["ITEM_ID", "ITEM_ID"] [Condition: CONDITION_CONSTRUCTOR, Condition: CONDITION_CONSTRUCTOR, Action: ACTION_CONSTRUCTOR, Action: ACTION_CONSTRUCTOR] ; +``` + +- if the state of an included item (from attribute `items`) changes the rule gets triggered +- when a rule is triggered each conditions receives the triggering item for check +- conditions checks are aggregated with `AND` operator +- if every condition returns true when checking each action will run using the triggering item + +### Constructors +#### Conditions + +| Name | Description | Structure | Example | +| ---------------------- |--------------------------------------------------| ------------------------------------| -------------------------| +| ItemStateNumberCheck | compares the state with NUMBER using the OPERATOR | ItemStateCheck OPERATOR NUMBER | ItemStateCheck < 5 | +| ItemStateTextCheck | compares the state with TEXT using the OPERATOR | ItemStateCheck OPERATOR TEXT | ItemStateCheck < "abc" | +| ItemStateChange | triggers if specified ITEM changed its value | ItemStateChange ITEM ItemStateChange | ItemStateChange item1 | +| ExpressionCondition | triggers if the LOGICAL_EXPRESSION is true | Expression LOGICAL_EXPRESSION | Expression (2<3) | + + + +#### Actions + +| Name | Description | Structure | Example | +| -------------------- |----------------------------------------------------------------------------------------------| -------------------------------| ------------------------------| +| TriggerRuleAction | triggers the rule with with specified RULE_NAME (conditions of RULE are still being checked) | TriggerRuleAction RULE_NAME | TriggerRuleAction testRule | +| NoopAction | no operation (just for testing) | NoopAction | NoopAction | +| SetStateFromConstantStringAction | apply specified VALUE to the ITEM | SetStateFromConstant ITEM VALUE | SetStateFromConstant item1 "LMNOP" | +| SetStateFromTriggeringItemAction | copies state from triggering device to specified ITEM | SetStateFromTrigger ITEM | SetStateFromTrigger item1 | +| MultiplyDoubleToStateAction | copies state multiplied by MULTIPLIER from triggering item to specified ITEM | SetStateFromTrigger ITEM MULTIPLIER | SetStateFromTrigger item1 2.0 | +| StateFromExpressionAction | apply the evaluated NUMBER_EXPRESSION as state to ITEM | StateFromExpression NUMBER_EXPRESSION ITEM | StateFromExpression (2.0 + 3.0) item2 | + +