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

WIP working towards incremental eraser version with ragconnect.

- apply style guide to build.gradle
- using locally built ragconnect with the latest fixes WIP
- refactor non-serializable stuff out of AST (SerializationExclusion)
- use incremental eval to trigger state sending (attribute: triggerStateUpdated), still WIP
- added test case to check that mqtt and rules work together (MqttRulesTest)
- added more logging while searching for errors
parent c6c8d574
Branches
No related tags found
1 merge request!21Draft: Replace mqtt handling with RagConnect
Pipeline #15484 failed
This commit is part of merge request !21. Comments created here will be created in the context of that merge request.
Showing
with 402 additions and 148 deletions
...@@ -2,6 +2,12 @@ plugins { ...@@ -2,6 +2,12 @@ plugins {
id 'eraser.java-jastadd-conventions' id 'eraser.java-jastadd-conventions'
} }
configurations {
ragconnect
}
dependencies { dependencies {
compileOnly group: 'de.tudresden.inf.st', name: 'ragconnect', version: '0.3.1' // TODO: use upstream ragconnect version once available
// ragconnect group: 'de.tudresden.inf.st', name: 'ragconnect', version: '1.0.0'
ragconnect fileTree(include: ['ragconnect.base-fatjar-1.0.1.jar'], dir: './libs')
} }
// --- Buildscripts (must be at the top) ---
buildscript { buildscript {
repositories.mavenLocal() repositories {
repositories.mavenCentral() mavenCentral()
}
dependencies { dependencies {
classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3' classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3'
} }
} }
// --- Plugin definitions ---
plugins { plugins {
id 'eraser.java-application-conventions' id 'eraser.java-application-conventions'
id 'eraser.java-ragconnect-conventions' id 'eraser.java-ragconnect-conventions'
} }
// --- Dependencies ---
repositories {
mavenCentral()
maven {
name 'gitlab-maven'
url 'https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven'
}
}
configurations { configurations {
relast
coverageGenClasspath coverageGenClasspath
} }
dependencies { dependencies {
jastadd2 "org.jastadd:jastadd:2.3.5" jastadd2 group: 'org.jastadd', name: 'jastadd2', version: "${jastadd_version}"
coverageGenClasspath group: 'de.tudresden.inf.st.jastadd', name: 'coverage-generator', version: '0.0.4' coverageGenClasspath group: 'de.tudresden.inf.st.jastadd', name: 'coverage-generator', version: '0.0.4'
relast group: 'org.jastadd', name: 'relast', version: "${relast_version}"
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_version}" api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_version}"
api group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.16' api group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.16'
implementation group: 'org.influxdb', name: 'influxdb-java', version: '2.20' implementation group: 'org.influxdb', name: 'influxdb-java', version: '2.20'
...@@ -29,7 +43,10 @@ dependencies { ...@@ -29,7 +43,10 @@ dependencies {
testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j_version}" testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j_version}"
} }
mainClassName = 'de.tudresden.inf.st.eraser.Main' // --- Preprocessors ---
File genSrc = file("src/gen/java")
sourceSets.main.java.srcDir genSrc
idea.module.generatedSourceDirs += genSrc
def ragConnectRelastFiles = fileTree('src/main/jastadd/') { def ragConnectRelastFiles = fileTree('src/main/jastadd/') {
include '**/*.relast' }.toList().toArray() include '**/*.relast' }.toList().toArray()
...@@ -37,35 +54,40 @@ String[] ragconnectArguments = [ ...@@ -37,35 +54,40 @@ String[] ragconnectArguments = [
'--o=src/gen/jastadd', '--o=src/gen/jastadd',
'--logReads', '--logReads',
'--logWrites', '--logWrites',
'--logIncremental',
// '--verbose', // '--verbose',
'--rootNode=Root', '--rootNode=Root',
// '--experimental-jastadd-329', '--experimental-jastadd-329',
// '--incremental=param', '--incremental=param',
// '--tracing=cache,flush', '--tracing=cache,flush',
'src/main/jastadd/shem.connect', 'src/main/jastadd/shem.connect',
'--List=JastAddList',
'--protocols=mqtt,java',
'--evaluationCounter'
] ]
task ragConnect(type: JavaExec) { task ragConnect(type: JavaExec) {
group = 'Build' group = 'Build'
main = 'org.jastadd.ragconnect.compiler.Compiler' main = 'org.jastadd.ragconnect.compiler.Compiler'
classpath = configurations.compileOnly classpath = configurations.ragconnect
args ragconnectArguments + ragConnectRelastFiles args ragconnectArguments + ragConnectRelastFiles
} }
String[] relastArguments = [ String[] relastArguments = [
"libs/relast.jar",
"--grammarName=./src/gen/jastadd/mainGen", "--grammarName=./src/gen/jastadd/mainGen",
"--useJastAddNames", "--useJastAddNames",
"--jastAddList=JastAddList", "--jastAddList=JastAddList",
"--resolverHelper", "--resolverHelper",
"--serializer=jackson",
"--file" "--file"
] ]
String[] relastFiles = ragConnectRelastFiles.collect { new File(it.toString().replace('/main/', '/gen/')) } String[] relastFiles = ragConnectRelastFiles.collect { new File(it.toString().replace('/main/', '/gen/')) }
task preprocess(type: JavaExec) { task preprocess(type: JavaExec) {
group = 'Build' group = 'Build'
main = "-jar" classpath = configurations.relast
main = 'org.jastadd.relast.compiler.Compiler'
args relastArguments + relastFiles args relastArguments + relastFiles
inputs.files relastFiles inputs.files relastFiles
...@@ -86,6 +108,7 @@ task generateCoverage(type: JavaExec) { ...@@ -86,6 +108,7 @@ task generateCoverage(type: JavaExec) {
args coverageGenArguments + relastFiles args coverageGenArguments + relastFiles
} }
// --- JastAdd ---
jastadd { jastadd {
configureModuleBuild() configureModuleBuild()
modules { modules {
...@@ -120,7 +143,13 @@ jastadd { ...@@ -120,7 +143,13 @@ jastadd {
} }
module = "eraser" module = "eraser"
extraJastAddOptions = ["--lineColumnNumbers", "--List=JastAddList"] extraJastAddOptions = [
"--lineColumnNumbers",
"--List=JastAddList",
"--incremental=param,debug",
"--tracing=cache,flush",
"--cache=all"
]
astPackage = 'de.tudresden.inf.st.eraser.jastadd.model' astPackage = 'de.tudresden.inf.st.eraser.jastadd.model'
genDir = 'src/gen/java' genDir = 'src/gen/java'
...@@ -132,25 +161,30 @@ jastadd { ...@@ -132,25 +161,30 @@ jastadd {
parser.genDir = "src/gen/java/de/tudresden/inf/st/eraser/jastadd/parser" parser.genDir = "src/gen/java/de/tudresden/inf/st/eraser/jastadd/parser"
} }
idea.module.generatedSourceDirs += file('src/gen/java') // --- Tests ---
task newTests(type: Test, dependsOn: testClasses) {
description = 'Run test tagged with tag "New"'
group = 'verification'
sourceSets.main { useJUnitPlatform {
java { includeTags 'New'
srcDir 'src/gen/java'
} }
} }
cleanGen.doFirst { // --- Versioning and Publishing ---
delete "src/gen/jastadd" mainClassName = 'de.tudresden.inf.st.eraser.Main'
delete "src/gen/java"
}
// --- Task order ---
preprocess.dependsOn ragConnect preprocess.dependsOn ragConnect
generateCoverage.dependsOn ragConnect generateCoverage.dependsOn ragConnect
generateAst.dependsOn preprocess generateAst.dependsOn preprocess
generateAst.dependsOn generateCoverage generateAst.dependsOn generateCoverage
generateAst.inputs.files file("./src/main/jastadd/mainGen.ast"), file("./src/main/jastadd/mainGen.jadd") generateAst.inputs.files file("./src/main/jastadd/mainGen.ast"), file("./src/main/jastadd/mainGen.jadd")
//compileJava.dependsOn jastadd
//
//// always run jastadd //// always run jastadd
//jastadd.outputs.upToDateWhen {false} //jastadd.outputs.upToDateWhen {false}
// --- Misc ---
cleanGen.doFirst {
delete "src/gen/jastadd"
delete "src/gen/java"
}
File added
...@@ -191,7 +191,11 @@ aspect ItemHandling { ...@@ -191,7 +191,11 @@ aspect ItemHandling {
//--- setStateFromColor --- //--- setStateFromColor ---
public abstract void Item.setStateFromColor(TupleHSB value); public abstract void Item.setStateFromColor(TupleHSB value);
public void ColorItem.setStateFromColor(TupleHSB value) { public void ColorItem.setStateFromColor(TupleHSB value) {
try {
this.setState(value.clone()); this.setState(value.clone());
} catch (CloneNotSupportedException e) {
// should not happen;
}
} }
public void DateTimeItem.setStateFromColor(TupleHSB value) { public void DateTimeItem.setStateFromColor(TupleHSB value) {
// there is no good way here // there is no good way here
...@@ -311,6 +315,24 @@ aspect ItemHandling { ...@@ -311,6 +315,24 @@ aspect ItemHandling {
} }
} }
//uncache Item.triggerStateUpdated();
syn String Item.triggerStateUpdated() {
logger.debug("triggerStateUpdated");
getStateAsString();
stateUpdated(sendState);
return getStateAsString();
}
//uncache NumberItem.triggerStateUpdated();
eq NumberItem.triggerStateUpdated() {
logger.debug("triggerStateUpdated (number-item)");
get_state();
stateUpdated(sendState);
return getStateAsString();
}
syn KeyValuePair Item.getFoo() {
return new KeyValuePair().setKey("State").setValue(getStateAsString());
}
//--- sendState --- //--- sendState ---
protected void Item.sendState() throws Exception { protected void Item.sendState() throws Exception {
...@@ -523,3 +545,63 @@ aspect ItemHandling { ...@@ -523,3 +545,63 @@ aspect ItemHandling {
} }
aspect Types {
/**
* Value class comprising three integral values hue, saturation, brightness ranging from 0 to 255.
*
* @author rschoene - Initial contribution
*/
public class TupleHSB {
public static TupleHSB of(int hue, int saturation, int brightness) {
return new TupleHSB()
.setHue(hue % 360)
.setSaturation(ensureBetweenZeroAndHundred(saturation))
.setBrightness(ensureBetweenZeroAndHundred(brightness));
}
private static int ensureBetweenZeroAndHundred(int value) {
return Math.max(0, Math.min(value, 100));
}
public TupleHSB withDifferentHue(int hue) {
return TupleHSB.of(hue, this.getSaturation(), this.getBrightness());
}
public TupleHSB withDifferentSaturation(int saturation) {
return TupleHSB.of(this.getHue(), saturation, this.getBrightness());
}
public TupleHSB withDifferentBrightness(int brightness) {
return TupleHSB.of(this.getHue(), this.getSaturation(), brightness);
}
public String toString() {
return String.format("%s,%s,%s", getHue(), getSaturation(), getBrightness());
}
public static TupleHSB parse(String s) {
String[] tokens = s.split(",");
return of(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TupleHSB tupleHSB = (TupleHSB) o;
return getHue() == tupleHSB.getHue() &&
getSaturation() == tupleHSB.getSaturation() &&
getBrightness() == tupleHSB.getBrightness();
}
@Override
public int hashCode() {
return Objects.hash(getHue(), getSaturation(), getBrightness());
}
}
refine ASTNode public TupleHSB TupleHSB.clone() throws CloneNotSupportedException {
return TupleHSB.of(getHue(), getSaturation(), getBrightness());
}
}
...@@ -3,7 +3,7 @@ MachineLearningRoot ::= [ActivityRecognition:MachineLearningModel] [PreferenceLe ...@@ -3,7 +3,7 @@ MachineLearningRoot ::= [ActivityRecognition:MachineLearningModel] [PreferenceLe
Activity ::= <Identifier:int> <Label:String> ; Activity ::= <Identifier:int> <Label:String> ;
abstract ChangeEvent ::= <Identifier:int> <Created:Instant> ChangedItem* ; abstract ChangeEvent ::= <Identifier:int> <Created:java.time.Instant> ChangedItem* ;
ChangedItem ::= <NewStateAsString:String> ; ChangedItem ::= <NewStateAsString:String> ;
rel ChangedItem.Item -> Item ; rel ChangedItem.Item -> Item ;
...@@ -19,12 +19,13 @@ rel MachineLearningModel.TargetItem* <-> Item.TargetInMachineLearningModel* ; ...@@ -19,12 +19,13 @@ rel MachineLearningModel.TargetItem* <-> Item.TargetInMachineLearningModel* ;
ExternalMachineLearningModel : MachineLearningModel ; ExternalMachineLearningModel : MachineLearningModel ;
abstract InternalMachineLearningModel : MachineLearningModel ::= <OutputApplication:DoubleDoubleFunction> ; abstract InternalMachineLearningModel : MachineLearningModel ;
// excluded: <OutputApplication:DoubleDoubleFunction>
MachineLearningResult ::= ItemUpdate* ; MachineLearningResult ::= ItemUpdate* ;
abstract ItemUpdate ::= ; abstract ItemUpdate ::= ;
rel ItemUpdate.Item -> Item ; rel ItemUpdate.Item -> Item ;
ItemUpdateColor : ItemUpdate ::= <NewHSB:TupleHSB> ; ItemUpdateColor : ItemUpdate ::= NewHSB:TupleHSB ;
ItemUpdateDouble : ItemUpdate ::= <NewValue:double> ; ItemUpdateDouble : ItemUpdate ::= <NewValue:double> ;
// ---------------- Neural Network ------------------------------ // ---------------- Neural Network ------------------------------
NeuralNetworkRoot : InternalMachineLearningModel ::= InputNeuron* HiddenNeuron* OutputLayer ; NeuralNetworkRoot : InternalMachineLearningModel ::= InputNeuron* HiddenNeuron* OutputLayer ;
OutputLayer ::= OutputNeuron* <Combinator:DoubleArrayDoubleFunction> ; OutputLayer ::= OutputNeuron* ;
// excluded: <Combinator:DoubleArrayDoubleFunction>
rel OutputLayer.AffectedItem -> Item ; rel OutputLayer.AffectedItem -> Item ;
abstract Neuron ::= Output:NeuronConnection* ; abstract Neuron ::= Output:NeuronConnection* ;
...@@ -12,7 +13,8 @@ rel NeuronConnection.Neuron <-> Neuron.Input* ; ...@@ -12,7 +13,8 @@ rel NeuronConnection.Neuron <-> Neuron.Input* ;
InputNeuron : Neuron ; InputNeuron : Neuron ;
rel InputNeuron.Item -> Item ; rel InputNeuron.Item -> Item ;
HiddenNeuron : Neuron ::= <ActivationFormula:DoubleArrayDoubleFunction> ; HiddenNeuron : Neuron ;
// excluded: <ActivationFormula:DoubleArrayDoubleFunction>
BiasNeuron : HiddenNeuron ; BiasNeuron : HiddenNeuron ;
OutputNeuron : HiddenNeuron ::= <Label:String> ; OutputNeuron : HiddenNeuron ::= <Label:String> ;
......
...@@ -7,7 +7,8 @@ rel ItemStateChangeCondition.Item -> Item; ...@@ -7,7 +7,8 @@ rel ItemStateChangeCondition.Item -> Item;
ExpressionCondition : Condition ::= LogicalExpression ; ExpressionCondition : Condition ::= LogicalExpression ;
abstract Action ; abstract Action ;
NoopAction : Action ; NoopAction : Action ;
LambdaAction : Action ::= <Lambda:Action2EditConsumer> ; LambdaAction : Action ::= ;
// excluded: <Lambda:Action2EditConsumer>
TriggerRuleAction : Action ; TriggerRuleAction : Action ;
rel TriggerRuleAction.Rule -> Rule ; rel TriggerRuleAction.Rule -> Rule ;
...@@ -18,10 +19,12 @@ rel SetStateAction.AffectedItem -> Item ; ...@@ -18,10 +19,12 @@ rel SetStateAction.AffectedItem -> Item ;
SetStateFromExpression : SetStateAction ::= NumberExpression ; SetStateFromExpression : SetStateAction ::= NumberExpression ;
SetStateFromConstantStringAction : SetStateAction ::= <NewState:String> ; SetStateFromConstantStringAction : SetStateAction ::= <NewState:String> ;
SetStateFromLambdaAction : SetStateAction ::= <NewStateProvider:NewStateProvider> ; SetStateFromLambdaAction : SetStateAction ;
// excluded: <NewStateProvider:NewStateProvider>
SetStateFromTriggeringItemAction : SetStateAction ::= ; SetStateFromTriggeringItemAction : SetStateAction ::= ;
SetStateFromItemsAction : SetStateAction ::= <Combinator:ItemsToStringFunction> ; SetStateFromItemsAction : SetStateAction ;
// excluded: <Combinator:ItemsToStringFunction>
rel SetStateFromItemsAction.SourceItem* -> Item ; rel SetStateFromItemsAction.SourceItem* -> Item ;
AddDoubleToStateAction : SetStateAction ::= <Increment:double> ; AddDoubleToStateAction : SetStateAction ::= <Increment:double> ;
......
aspect SerializationExclusion {
// add additional information to types of the AST not directly being part of it (excluding them from serialization)
class LambdaAction {
Action2EditConsumer _Lambda;
public Action2EditConsumer getLambda() { return _Lambda; }
public LambdaAction setLambda(Action2EditConsumer value) { _Lambda = value; return this; }
}
class SetStateFromLambdaAction {
NewStateProvider _NewStateProvider;
public NewStateProvider getNewStateProvider() { return _NewStateProvider; }
public SetStateFromLambdaAction setNewStateProvider(NewStateProvider value) { _NewStateProvider = value; return this; }
}
class SetStateFromItemsAction {
ItemsToStringFunction _Combinator;
public ItemsToStringFunction getCombinator() { return _Combinator; }
public SetStateFromItemsAction setCombinator(ItemsToStringFunction value) { _Combinator = value; return this; }
}
class InternalMachineLearningModel {
DoubleDoubleFunction _OutputApplication;
public DoubleDoubleFunction getOutputApplication() { return _OutputApplication; }
public InternalMachineLearningModel setOutputApplication(DoubleDoubleFunction value) { _OutputApplication = value; return this; }
}
class OutputLayer {
DoubleArrayDoubleFunction _Combinator;
public DoubleArrayDoubleFunction getCombinator() { return _Combinator; }
public OutputLayer setCombinator(DoubleArrayDoubleFunction value) { _Combinator = value; return this; }
}
class HiddenNeuron {
DoubleArrayDoubleFunction _ActivationFormula;
public DoubleArrayDoubleFunction getActivationFormula() { return _ActivationFormula; }
public HiddenNeuron setActivationFormula(DoubleArrayDoubleFunction value) { _ActivationFormula = value; return this; }
}
}
...@@ -17,7 +17,7 @@ aspect MQTT { ...@@ -17,7 +17,7 @@ aspect MQTT {
syn ExternalHost MqttRoot.getHost() = new ExternalHost(); syn ExternalHost MqttRoot.getHost() = new ExternalHost();
// --- connectAllItems --- // --- connectAllItems ---
public boolean SmartHomeEntityModel.connectAllItems() throws IOException { public boolean SmartHomeEntityModel.connectAllItems() throws java.io.IOException {
MqttRoot mqttRoot = getRoot().getMqttRoot(); MqttRoot mqttRoot = getRoot().getMqttRoot();
ExternalHost host = mqttRoot.getHost(); ExternalHost host = mqttRoot.getHost();
// TODO user/password not used yet (not supported by ragconnect yet) // TODO user/password not used yet (not supported by ragconnect yet)
...@@ -26,34 +26,38 @@ aspect MQTT { ...@@ -26,34 +26,38 @@ aspect MQTT {
boolean success = true; boolean success = true;
for (Item item : this.items()) { for (Item item : this.items()) {
String suffix = item.getTopicString().isBlank() ? item.getID() : item.getTopicString(); String suffix = item.getTopicString().isBlank() ? item.getID() : item.getTopicString();
getRoot().ragconnectJavaRegisterConsumer(suffix, bytes -> {
String state = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
item.stateUpdated(item.sendState);
System.out.println("java handler activated for " + item.getID() + " with '" + state + "'");
});
ConnectReceive connectReceive; ConnectReceive connectReceive;
ConnectSend connectSend;
if (item.isItemWithDoubleState()) { if (item.isItemWithDoubleState()) {
connectReceive = item.asItemWithDoubleState()::connect_state; connectReceive = item.asItemWithDoubleState()::connect_state;
connectSend = item.asItemWithDoubleState()::connect_state;
} else if (item.isItemWithBooleanState()) { } else if (item.isItemWithBooleanState()) {
connectReceive = item.asItemWithBooleanState()::connect_state; connectReceive = item.asItemWithBooleanState()::connect_state;
connectSend = item.asItemWithBooleanState()::connect_state;
} else if (item.isItemWithStringState()) { } else if (item.isItemWithStringState()) {
connectReceive = item.asItemWithStringState()::connect_state; connectReceive = item.asItemWithStringState()::connect_state;
connectSend = item.asItemWithStringState()::connect_state;
} else { } else {
// unsupported item type // unsupported item type
continue; continue;
} }
success &= connectReceive.apply(prefix + mqttRoot.getIncomingPrefix() + suffix) & success &= connectReceive.apply(prefix + mqttRoot.getIncomingPrefix() + suffix) &
connectSend.apply(prefix + mqttRoot.getOutgoingPrefix() + suffix, false); item.connectTriggerStateUpdated("java://localhost/" + suffix, true) &
item.connectTriggerStateUpdated("mqtt://localhost/trigger/" + suffix, true) &
item.connectFoo("mqtt://localhost/foo/" + suffix, true)
;
} }
return success; return success;
} }
class SmartHomeEntityModel { class SmartHomeEntityModel {
interface ConnectReceive { interface ConnectReceive {
boolean apply(String uriString) throws IOException; boolean apply(String uriString) throws java.io.IOException;
} }
interface ConnectSend { interface ConnectSend {
boolean apply(String uriString, boolean writeCurrentValue) throws IOException; boolean apply(String uriString, boolean writeCurrentValue) throws java.io.IOException;
} }
} }
} }
receive ItemWithDoubleState._state using StringToDouble ; receive ItemWithDoubleState._state using StringToDouble ;
send ItemWithDoubleState._state using DoubleToString ; //send ItemWithDoubleState._state using DoubleToString ;
receive ItemWithBooleanState._state using StringToBoolean ;
send ItemWithBooleanState._state using BooleanToString ;
receive ItemWithStringState._state ;
//send ItemWithStringState._state ;
send Item.triggerStateUpdated(String);
send Item.Foo;
// Mappings
StringToDouble maps String s to double {: StringToDouble maps String s to double {:
return Double.parseDouble(s); return Double.parseDouble(s);
:} :}
...@@ -9,9 +20,6 @@ DoubleToString maps double d to String {: ...@@ -9,9 +20,6 @@ DoubleToString maps double d to String {:
return Double.toString(d); return Double.toString(d);
:} :}
receive ItemWithBooleanState._state using StringToBoolean ;
send ItemWithBooleanState._state using BooleanToString ;
StringToBoolean maps String s to boolean {: StringToBoolean maps String s to boolean {:
return Boolean.parseBoolean(s); return Boolean.parseBoolean(s);
:} :}
...@@ -19,6 +27,3 @@ StringToBoolean maps String s to boolean {: ...@@ -19,6 +27,3 @@ StringToBoolean maps String s to boolean {:
BooleanToString maps boolean b to String {: BooleanToString maps boolean b to String {:
return Boolean.toString(b); return Boolean.toString(b);
:} :}
receive ItemWithStringState._state ;
send ItemWithStringState._state ;
...@@ -70,5 +70,4 @@ aspect SmartHomeEntityModel { ...@@ -70,5 +70,4 @@ aspect SmartHomeEntityModel {
return result; return result;
}); });
} }
} }
...@@ -29,15 +29,27 @@ rel Channel.LinkedItem* <-> Item.Channel? ; ...@@ -29,15 +29,27 @@ rel Channel.LinkedItem* <-> Item.Channel? ;
Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ; Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ;
ParameterDefaultValue ::= <Value:String> ; ParameterDefaultValue ::= <Value:String> ;
abstract Item : LabelledModelElement ::= <_fetched_data:boolean> <TopicString> [MetaData] /ItemObserver/ /LastChanged/; abstract Item : LabelledModelElement ::= <_fetched_data:boolean> <TopicString> [MetaData] /ItemObserver/ /LastChanged/ /Foo:KeyValuePair/;
rel Item.Category? <-> ItemCategory.Items* ; rel Item.Category? <-> ItemCategory.Items* ;
rel Item.FrequencySetting? -> FrequencySetting ; rel Item.FrequencySetting? -> FrequencySetting ;
//abstract AbstractItemWithBooleanState : Item ::= <State:boolean> ;
//abstract AbstractItemWithStringState : Item ::= <State:String> ;
//abstract AbstractItemWithDoubleState : Item ::= <State:double> ;
//abstract AbstractColorItem : Item ::= State:TupleHSB ;
//abstract AbstractDateTimeItem : Item ::= <State:java.time.Instant> ;
//
//abstract ItemWithBooleanState : AbstractItemWithBooleanState ;
//abstract ItemWithStringState : AbstractItemWithStringState ;
//abstract ItemWithDoubleState : AbstractItemWithDoubleState ;
//ColorItem : AbstractColorItem ;
//DateTimeItem : AbstractDateTimeItem ;
abstract ItemWithBooleanState : Item ::= <_state:boolean> ; abstract ItemWithBooleanState : Item ::= <_state:boolean> ;
abstract ItemWithStringState : Item ::= <_state:String> ; abstract ItemWithStringState : Item ::= <_state:String> ;
abstract ItemWithDoubleState : Item ::= <_state:double> ; abstract ItemWithDoubleState : Item ::= <_state:double> ;
ColorItem : Item ::= <_state:TupleHSB> ; ColorItem : Item ::= _state:TupleHSB ;
DateTimeItem : Item ::= <_state:Instant> ; DateTimeItem : Item ::= <_state:java.time.Instant> ;
ContactItem : ItemWithBooleanState ; ContactItem : ItemWithBooleanState ;
DimmerItem : ItemWithDoubleState ; DimmerItem : ItemWithDoubleState ;
ImageItem : ItemWithStringState ; ImageItem : ItemWithStringState ;
...@@ -52,12 +64,14 @@ ActivityItem : ItemWithDoubleState ; ...@@ -52,12 +64,14 @@ ActivityItem : ItemWithDoubleState ;
ItemPrototype : ItemWithStringState ::= ItemWithCorrectType:Item ; // only used for parsing ItemPrototype : ItemWithStringState ::= ItemWithCorrectType:Item ; // only used for parsing
ItemPlaceHolder : ItemWithStringState ; // only used for parsing ItemPlaceHolder : ItemWithStringState ; // only used for parsing
TupleHSB ::= <Hue:int> <Saturation:int> <Brightness:int> ;
MetaData ::= KeyValuePair* ; MetaData ::= KeyValuePair* ;
KeyValuePair ::= <Key:String> <Value:String> ; KeyValuePair ::= <Key:String> <Value:String> ;
ItemCategory ::= <Name:String> ; ItemCategory ::= <Name:String> ;
LastChanged ::= <Value:Instant> ; LastChanged ::= <Value:java.time.Instant> ;
Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ; Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ;
rel Group.FrequencySetting? -> FrequencySetting ; rel Group.FrequencySetting? -> FrequencySetting ;
......
...@@ -9,7 +9,6 @@ import de.tudresden.inf.st.eraser.jastadd.model.Root; ...@@ -9,7 +9,6 @@ import de.tudresden.inf.st.eraser.jastadd.model.Root;
import de.tudresden.inf.st.eraser.openhab2.OpenHab2Importer; import de.tudresden.inf.st.eraser.openhab2.OpenHab2Importer;
import de.tudresden.inf.st.eraser.util.ParserUtils; import de.tudresden.inf.st.eraser.util.ParserUtils;
import de.tudresden.inf.st.eraser.util.TestUtils; import de.tudresden.inf.st.eraser.util.TestUtils;
import org.apache.logging.log4j.LogManager;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
......
package de.tudresden.inf.st.eraser.jastadd.model;
import java.util.Objects;
/**
* Value class comprising three integral values hue, saturation, brightness ranging from 0 to 255.
*
* @author rschoene - Initial contribution
*/
public class TupleHSB implements Cloneable {
private int hue;
private int saturation;
private int brightness;
public static TupleHSB of(int hue, int saturation, int brightness) {
TupleHSB result = new TupleHSB();
result.hue = hue % 360;
result.saturation = ensureBetweenZeroAndHundred(saturation);
result.brightness = ensureBetweenZeroAndHundred(brightness);
return result;
}
private static int ensureBetweenZeroAndHundred(int value) {
return Math.max(0, Math.min(value, 100));
}
public int getHue() {
return hue;
}
public int getSaturation() {
return saturation;
}
public int getBrightness() {
return brightness;
}
public TupleHSB withDifferentHue(int hue) {
return TupleHSB.of(hue, this.saturation, this.brightness);
}
public TupleHSB withDifferentSaturation(int saturation) {
return TupleHSB.of(this.hue, saturation, this.brightness);
}
public TupleHSB withDifferentBrightness(int brightness) {
return TupleHSB.of(this.hue, this.saturation, brightness);
}
public String toString() {
return String.format("%s,%s,%s", hue, saturation, brightness);
}
public static TupleHSB parse(String s) {
String[] tokens = s.split(",");
return of(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]));
}
@SuppressWarnings("MethodDoesntCallSuperMethod")
public TupleHSB clone() {
return TupleHSB.of(hue, saturation, brightness);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TupleHSB tupleHSB = (TupleHSB) o;
return hue == tupleHSB.hue &&
saturation == tupleHSB.saturation &&
brightness == tupleHSB.brightness;
}
@Override
public int hashCode() {
return Objects.hash(hue, saturation, brightness);
}
}
...@@ -12,6 +12,8 @@ import java.util.concurrent.TimeUnit; ...@@ -12,6 +12,8 @@ import java.util.concurrent.TimeUnit;
*/ */
public class TestUtils { public class TestUtils {
public static final double DELTA = 0.01;
public static class ModelAndItem { public static class ModelAndItem {
public SmartHomeEntityModel model; public SmartHomeEntityModel model;
public NumberItem item; public NumberItem item;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</RollingFile> </RollingFile>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Root level="info"> <Root level="debug">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/> <AppenderRef ref="RollingFile"/>
</Root> </Root>
......
package de.tudresden.inf.st.eraser;
import de.tudresden.inf.st.eraser.jastadd.model.*;
import de.tudresden.inf.st.eraser.util.ParserUtils;
import de.tudresden.inf.st.eraser.util.TestUtils;
import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import static de.tudresden.inf.st.eraser.util.TestUtils.getMqttHost;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for the combination of MQTT and rules.
*
* @author rschoene - Initial contribution
*/
@Tag("mqtt")
@Tag("New")
public class MqttRulesTest {
private static final Logger logger = LogManager.getLogger(MqttRulesTest.class);
private static final String topic = "a/b";
private MqttHandler handler;
@BeforeEach
public void activateHandler() throws IOException {
handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost());
assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker");
handler.publish("test", ("before at " + Instant.now()).getBytes(StandardCharsets.UTF_8));
}
@AfterEach
public void deactivateHandler() {
if (handler != null) {
handler.publish("test", ("after at " + Instant.now()).getBytes(StandardCharsets.UTF_8));
handler.close();
}
}
@Test
public void receiveItemTriggerRule(TestInfo testInfo) throws Exception {
String displayName = testInfo.getDisplayName();
handler.publish("test", ("starting " + displayName).getBytes(StandardCharsets.UTF_8));
// Given model with two items, one to receive updates from mqtt and another to be updated
ModelAndItem mai = MqttTests.createModelAndSetupMqttRoot();
Root root = mai.model.getRoot();
String incomingPrefix = root.getMqttRoot().getIncomingPrefix();
String usedTopic = incomingPrefix + topic;
NumberItem receivingItem = mai.item;
receivingItem.setTopicString(topic);
NumberItem itemToBeUpdated = TestUtils.addItemTo(mai.model, 2.0, true);
assertTrue(mai.model.connectAllItems(), "Could not connect items");
TestUtils.waitForMqtt();
// ... and a rule is defined for item to be updated
Rule rule = new Rule();
SetStateFromExpression action = new SetStateFromExpression();
// TODO item1 should be referred to as triggering item
action.setNumberExpression(ParserUtils.parseNumberExpression("(" + receivingItem.getID() + " + 7)", root));
action.setAffectedItem(itemToBeUpdated);
rule.addAction(action);
RulesTest.CountingAction counter = new RulesTest.CountingAction();
rule.addAction(counter);
// root.ragconnectJavaRegisterConsumer(itemToBeUpdated.getID(), bytes -> {
// String state = new String(bytes, StandardCharsets.UTF_8);
// itemToBeUpdated.stateUpdated(itemToBeUpdated.sendState);
// });
root.addRule(rule);
rule.activateFor(receivingItem);
// // try manually
// logger.warn(counter.counters);
// receivingItem.triggerStateUpdated();
// logger.warn(counter.counters);
// receivingItem.set_state(3.0);
// receivingItem.triggerStateUpdated();
// logger.warn(counter.counters);
// receivingItem.set_state(9.0);
// logger.warn(counter.counters);
// When a message is published on the topic the first item is receiving
// itemToBeUpdated.dumpDependencies();
logger.debug("before '{}' + {}", receivingItem.getState(), root.ragconnectEvaluationCounterSummary());
handler.publish(usedTopic, "4.0".getBytes(StandardCharsets.UTF_8));
TestUtils.waitForMqtt();
logger.debug("after '{}' + {}", receivingItem.getState(), root.ragconnectEvaluationCounterSummary());
// itemToBeUpdated.dumpDependencies();
// Then the state of both items should be updated
logger.warn(counter.counters);
// v-- this needs to be called automatically : TODO
// receivingItem.triggerStateUpdated();
// logger.warn(counter.counters);
assertEquals(4.0, receivingItem.getState(), TestUtils.DELTA);
assertEquals(11.0, itemToBeUpdated.getState(), TestUtils.DELTA);
}
}
...@@ -6,6 +6,7 @@ import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; ...@@ -6,6 +6,7 @@ import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -23,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; ...@@ -23,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("mqtt") @Tag("mqtt")
public class MqttTests { public class MqttTests {
private static final String outgoingPrefix = "out";
private static final String firstPart = "a"; private static final String firstPart = "a";
private static final String alternativeFirstPart = "x"; private static final String alternativeFirstPart = "x";
private static final String secondPart = "b"; private static final String secondPart = "b";
...@@ -32,16 +32,17 @@ public class MqttTests { ...@@ -32,16 +32,17 @@ public class MqttTests {
private static final double thirdState = 3.0; private static final double thirdState = 3.0;
@Test @Test
public void itemUpdateSend1() throws Exception { public void sendSingleItem() throws Exception {
// Given model with single item // Given model with single item
String expectedTopic = outgoingPrefix + "/" + firstPart + "/" + secondPart; ModelAndItem modelAB = createModelAndSetupMqttRoot();
String outgoingPrefix = modelAB.model.getRoot().getMqttRoot().getOutgoingPrefix();
String expectedTopic = outgoingPrefix + firstPart + "/" + secondPart;
MqttHandler handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost()); MqttHandler handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost());
assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker"); assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker");
List<String> messages = new ArrayList<>(); List<String> messages = new ArrayList<>();
handler.newConnection(expectedTopic, bytes -> messages.add(new String(bytes))); handler.newConnection(expectedTopic, bytes -> messages.add(new String(bytes)));
ModelAndItem modelAB = createModelAndSetupMqttRoot();
NumberItem sut = modelAB.item; NumberItem sut = modelAB.item;
sut.setTopicString(firstPart + "/" + secondPart); sut.setTopicString(firstPart + "/" + secondPart);
assertTrue(modelAB.model.connectAllItems(), "Could not connect items"); assertTrue(modelAB.model.connectAllItems(), "Could not connect items");
...@@ -65,10 +66,12 @@ public class MqttTests { ...@@ -65,10 +66,12 @@ public class MqttTests {
} }
@Test @Test
public void itemUpdateSend2() throws Exception { public void sendItemsDifferentTopic() throws Exception {
// Given model with two items, each with a different topic // Given model with two items, each with a different topic
String expectedTopic1 = outgoingPrefix + "/" + firstPart + "/" + secondPart; ModelAndItem modelAB = createModelAndSetupMqttRoot();
String expectedTopic2 = outgoingPrefix + "/" + alternativeFirstPart + "/" + secondPart; String outgoingPrefix = modelAB.model.getRoot().getMqttRoot().getOutgoingPrefix();
String expectedTopic1 = outgoingPrefix + firstPart + "/" + secondPart;
String expectedTopic2 = outgoingPrefix + alternativeFirstPart + "/" + secondPart;
MqttHandler handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost()); MqttHandler handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost());
assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker"); assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker");
...@@ -77,7 +80,6 @@ public class MqttTests { ...@@ -77,7 +80,6 @@ public class MqttTests {
handler.newConnection(expectedTopic1, bytes -> messagesTopic1.add(new String(bytes))); handler.newConnection(expectedTopic1, bytes -> messagesTopic1.add(new String(bytes)));
handler.newConnection(expectedTopic2, bytes -> messagesTopic2.add(new String(bytes))); handler.newConnection(expectedTopic2, bytes -> messagesTopic2.add(new String(bytes)));
ModelAndItem modelAB = createModelAndSetupMqttRoot();
NumberItem item1 = modelAB.item; NumberItem item1 = modelAB.item;
NumberItem item2 = TestUtils.addItemTo(modelAB.model, 0, true); NumberItem item2 = TestUtils.addItemTo(modelAB.model, 0, true);
item1.setTopicString(firstPart + "/" + secondPart); item1.setTopicString(firstPart + "/" + secondPart);
...@@ -108,12 +110,35 @@ public class MqttTests { ...@@ -108,12 +110,35 @@ public class MqttTests {
assertThat(messagesTopic2).contains(Double.toString(thirdState)); assertThat(messagesTopic2).contains(Double.toString(thirdState));
} }
private ModelAndItem createModelAndSetupMqttRoot() { @Test
public void receiveSingleItem() throws Exception {
// Given model with single item
ModelAndItem modelAB = createModelAndSetupMqttRoot();
String incomingPrefix = modelAB.model.getRoot().getMqttRoot().getIncomingPrefix();
String usedTopic = incomingPrefix + firstPart + "/" + secondPart;
MqttHandler handler = new MqttHandler().dontSendWelcomeMessage().setHost(getMqttHost());
assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS), "Could not connect to MQTT broker");
NumberItem sut = modelAB.item;
sut.setTopicString(firstPart + "/" + secondPart);
assertTrue(modelAB.model.connectAllItems(), "Could not connect items");
TestUtils.waitForMqtt();
// When a message is published on the topic the item is receiving
handler.publish(usedTopic, "4.0".getBytes(StandardCharsets.UTF_8));
TestUtils.waitForMqtt();
// Then the state of the item should be updated
assertEquals(4, sut.getState(), TestUtils.DELTA);
}
static ModelAndItem createModelAndSetupMqttRoot() {
ModelAndItem mai = TestUtils.createModelAndItem(0, true); ModelAndItem mai = TestUtils.createModelAndItem(0, true);
SmartHomeEntityModel model = mai.model; SmartHomeEntityModel model = mai.model;
MqttRoot mqttRoot = new MqttRoot(); MqttRoot mqttRoot = new MqttRoot();
mqttRoot.setIncomingPrefix("inc"); mqttRoot.setIncomingPrefix("inc");
mqttRoot.setOutgoingPrefix(outgoingPrefix); mqttRoot.setOutgoingPrefix("out");
mqttRoot.getHost().setHostName(getMqttHost()).setPort(1883); mqttRoot.getHost().setHostName(getMqttHost()).setPort(1883);
mqttRoot.ensureCorrectPrefixes(); mqttRoot.ensureCorrectPrefixes();
model.getRoot().setMqttRoot(mqttRoot); model.getRoot().setMqttRoot(mqttRoot);
......
...@@ -5,6 +5,8 @@ import de.tudresden.inf.st.eraser.jastadd.model.*; ...@@ -5,6 +5,8 @@ import de.tudresden.inf.st.eraser.jastadd.model.*;
import de.tudresden.inf.st.eraser.jastadd.model.Action; import de.tudresden.inf.st.eraser.jastadd.model.Action;
import de.tudresden.inf.st.eraser.util.ParserUtils; import de.tudresden.inf.st.eraser.util.ParserUtils;
import de.tudresden.inf.st.eraser.util.TestUtils; import de.tudresden.inf.st.eraser.util.TestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList; import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
...@@ -30,13 +32,20 @@ import static org.junit.jupiter.api.Assertions.*; ...@@ -30,13 +32,20 @@ import static org.junit.jupiter.api.Assertions.*;
* @author rschoene - Initial contribution * @author rschoene - Initial contribution
*/ */
public class RulesTest { public class RulesTest {
private static Logger logger = LogManager.getLogger(RulesTest.class);
private static final double DELTA = 0.01d; private static final double DELTA = 0.01d;
static class CountingAction extends NoopAction { static class CountingAction extends NoopAction {
final Map<Item, AtomicInteger> counters = new HashMap<>(); final Map<Item, AtomicInteger> counters = new HashMap<>();
final boolean verbose;
CountingAction() { CountingAction() {
this(false);
}
CountingAction(boolean verbose) {
this.verbose = verbose;
reset(); reset();
} }
...@@ -47,6 +56,7 @@ public class RulesTest { ...@@ -47,6 +56,7 @@ public class RulesTest {
@Override @Override
public void applyFor(Item item) { public void applyFor(Item item) {
getAtomic(item).addAndGet(1); getAtomic(item).addAndGet(1);
logger.debug("Rule activated (counter = {})", get(item));
} }
int get(Item item) { int get(Item item) {
...@@ -606,7 +616,7 @@ public class RulesTest { ...@@ -606,7 +616,7 @@ public class RulesTest {
NumberItem item2 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 3, true); NumberItem item2 = TestUtils.addItemTo(root.getSmartHomeEntityModel(), 3, true);
Rule rule = new Rule(); Rule rule = new Rule();
rule.addAction(new SetStateFromLambdaAction(item2, provider)); rule.addAction(new SetStateFromLambdaAction().setNewStateProvider(provider).setAffectedItem(item2));
CountingAction counter = new CountingAction(); CountingAction counter = new CountingAction();
rule.addAction(counter); rule.addAction(counter);
root.addRule(rule); root.addRule(rule);
...@@ -674,7 +684,7 @@ public class RulesTest { ...@@ -674,7 +684,7 @@ public class RulesTest {
StringItem affectedItem = addStringItem(root.getSmartHomeEntityModel(), "1"); StringItem affectedItem = addStringItem(root.getSmartHomeEntityModel(), "1");
Rule rule = new Rule(); Rule rule = new Rule();
SetStateFromItemsAction action = new SetStateFromItemsAction(items -> SetStateFromItemsAction action = new SetStateFromItemsAction().setCombinator(items ->
Long.toString(StreamSupport.stream(items.spliterator(), false) Long.toString(StreamSupport.stream(items.spliterator(), false)
.mapToLong(inner -> (long) inner.asItemWithDoubleState().getState()) .mapToLong(inner -> (long) inner.asItemWithDoubleState().getState())
.sum())); .sum()));
......
...@@ -87,7 +87,7 @@ public class TupleHSBTest { ...@@ -87,7 +87,7 @@ public class TupleHSBTest {
} }
@Test @Test
public void testClone() { public void testClone() throws CloneNotSupportedException {
TupleHSB one = TupleHSB.of(361,2,3); TupleHSB one = TupleHSB.of(361,2,3);
TupleHSB two = TupleHSB.of(50,123,100); TupleHSB two = TupleHSB.of(50,123,100);
TupleHSB clone = one.clone(); TupleHSB clone = one.clone();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment