diff --git a/.gitignore b/.gitignore index ae5f085df619e4faf11263c8af42121396ec16d9..cddf03602a35709c94e0d191f9fe37af69e5757e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .gradle/ .idea/ build/dependencyUpdates/ +venv/ +*/out/ +logs/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e77a76053345e34f70811b6498a8758dbf7d305..918f65ab8164ff97f01d7e37dc1fa86b7bfd3766 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,15 +4,23 @@ stages: - report variables: + # Instruct Testcontainers to use the daemon of DinD. + DOCKER_HOST: "unix:///var/run/docker.sock" +# # Improve performance with overlayfs. +# DOCKER_DRIVER: overlay2 GRADLE_OPTS: "-Dorg.gradle.daemon=false" TEST_REPORTS: "/builds/OpenLicht/eraser/eraser-base/build/reports/tests/test/" + TEST_LOG: "/builds/OpenLicht/eraser/eraser-base/logs/eraser-test.log" JACOCO_REPORT: "/builds/OpenLicht/eraser/eraser-base/build/reports/jacoco/test/jacocoTestReport.xml" + TESTCONTAINERS_RYUK_DISABLED: "true" before_script: - export GRADLE_USER_HOME=`pwd`/.gradle build: - image: gradle:jdk8 + image: openjdk:8 + tags: + - docker stage: build script: - ./gradlew --console=plain --build-cache assemble @@ -24,7 +32,9 @@ build: - .gradle test: - image: gradle:jdk8 + image: openjdk:8 + tags: + - docker stage: test script: - ./gradlew --continue --console=plain check jacocoTestReport @@ -37,11 +47,14 @@ test: artifacts: when: always paths: + - $TEST_LOG - $TEST_REPORTS - $JACOCO_REPORT coverage: image: python:3.7.1-alpine + tags: + - docker stage: report dependencies: - test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..e77282f413a7cf57a9ad0b61b13d7397531c013f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ragdoc-view"] + path = ragdoc-view + url = ../../jastadd/ragdoc-view/ diff --git a/README.md b/README.md index 547af86940b786d79ef41894c0c19ba03bcd636a..ce1aa70bdc28d9403a709ccd0bfa1379a2903265 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,7 @@ There are a few subprojects in this repository: ## Setup and Building -This project uses Gradle as the build tool. Please use the provided Gradle wrappers instead of an installed one. - -- Clone the repository, either using [https](https://git-st.inf.tu-dresden.de/OpenLicht/eraser.git) or [ssh](git@git-st.inf.tu-dresden.de:OpenLicht/eraser.git) -- (Optional) Build all subprojects: `./gradlew build` +This project uses Gradle as the build tool. For detailed information, see [setup guidelines](/../wikis/setup) ## Running the demo diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..54c5c0b024dab04da2a43d4135ed67ed2a670b46 --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1,2 @@ +/build/ +logs/ diff --git a/benchmark/build.gradle b/benchmark/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..dfcc1d42edbaf81885917bd8b31573056ba29dbb --- /dev/null +++ b/benchmark/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'application' + +dependencies { + compile project(':eraser-base') + compile project(':feedbackloop.learner_backup') + compile project(':datasets') + testImplementation 'junit:junit:4.12' + // https://mvnrepository.com/artifact/org.hamcrest/java-hamcrest + // https://mvnrepository.com/artifact/org.hamcrest/hamcrest-core + testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.3' + implementation group: 'com.opencsv', name: 'opencsv', version: '4.1' + // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient + compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.0-alpha4' + testCompile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' + // https://mvnrepository.com/artifact/org.apache.httpcomponents/fluent-hc + compile group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.2.1' + // https://mvnrepository.com/artifact/junit/junit + testCompile group: 'junit', name: 'junit', version: '4.4' + +} + +run { + mainClassName = 'de.tudresden.inf.st.eraser.benchmark.Main' + standardInput = System.in + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} diff --git a/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Benchmark.java b/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Benchmark.java new file mode 100644 index 0000000000000000000000000000000000000000..2c1d64d5be93238589167e0c474218d3b22a952f --- /dev/null +++ b/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Benchmark.java @@ -0,0 +1,146 @@ +package de.tudresden.inf.st.eraser.benchmark; + +import com.opencsv.CSVReader; +import org.apache.http.HttpResponse; +import org.apache.http.client.fluent.Request; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.io.File; +import java.io.FileReader; +import java.util.Arrays; + +public class Benchmark { + + //read every 5 s from csv + //activity CSV + /** + * Col 1: smartphone acceleration x + * Col 2: smartphone acceleration y + * Col 3: smartphone acceleration z + * Col 4: smartphone rotation x + * Col 5: smartphone rotation y + * Col 6: smartphone rotation z + * Col 7: watch acceleration x + * Col 8: watch acceleration y + * Col 9: watch acceleration z + * Col 10: watch rotation x + * Col 11: watch rotation y + * Col 12: watch rotation z/*/ + //preference CSV + /** + * Col 1: Activity + * Col 2: watch brightness range "bright, medium, dimmer, dark"*/ + + private String a_csv_file_path; + private String p_csv_file_path; + private static final Logger logger = LogManager.getLogger(Benchmark.class); + private static final String ERASER_ITEM_URI = "http://localhost:4567/model/items/"; + //TODO ITEM_NAME HAS TO BE DISCUSSED + private static final String[] ACTIVITY_ITEM_NAME = { + "m_accel_x", "m_accel_y", "m_accel_z", "m_rotation_x", + "m_rotation_y", "m_rotation_z", "w_accel_x", "w_accel_y", + "w_accel_z", "w_rotation_x", "w_rotation_y", "w_rotation_z"}; + private static final String[] PREFERENCE_ITEM_NAME = { + "w_brightness" + }; + private static boolean flag2 = true; + //csv_type is activity or preference + Benchmark(String a_csv_file_path, String p_csv_file_path) { + this.a_csv_file_path = a_csv_file_path; + this.p_csv_file_path = p_csv_file_path; + } + + void start(){ + String PREFERENCE_URL = "http://localhost:4567/model/items/iris1_item/state"; + String ACTIVITY_URL = "http://localhost:4567/activity/current"; + int TIME_PERIOD = 5000; + File a_file; + File p_file; + FileReader a_file_reader; + FileReader p_file_reader; + CSVReader a_csv_reader; + CSVReader p_csv_reader; + String[] a_next_record; + String[] p_next_record; + boolean flag1; + a_file=new File(a_csv_file_path); + p_file= new File(p_csv_file_path); + try { + a_file_reader =new FileReader(a_file); + p_file_reader=new FileReader(p_file); + a_csv_reader = new CSVReader(a_file_reader); + p_csv_reader = new CSVReader(p_file_reader); + while ((((a_next_record = a_csv_reader.readNext())!= null) && flag2)){ + try{Thread.sleep(TIME_PERIOD);}catch (InterruptedException e){e.printStackTrace();} + String[] values = Arrays.copyOf(a_next_record,12); + setNewValue(values, ACTIVITY_ITEM_NAME,"activity"); + HttpResponse response= Request.Get(ACTIVITY_URL).execute().returnResponse(); + String status = response.getStatusLine().toString(); + if(status.contains("200")){ + flag1 = true; + logger.info("activity should be (read direct from CSV): " + a_next_record[12]); + logger.info(EntityUtils.toString(response.getEntity())); + logger.info("get activity from web server: response 200 ok"); + }else{ + flag1 = false; + flag2 = false; + logger.info("can not get the activity from the web server"); + } + while((((p_next_record = p_csv_reader.readNext()) != null) && flag1)) { + try{Thread.sleep(TIME_PERIOD);}catch (InterruptedException e){e.printStackTrace();} + String[] values1 = Arrays.copyOf(p_next_record,2); + setNewValue(values1, PREFERENCE_ITEM_NAME,"preference"); + HttpResponse response1= Request.Get(PREFERENCE_URL).execute().returnResponse(); + String status1=response1.getStatusLine().toString(); + if(status1.contains("200")){ + flag2 = true; + logger.info("get the iris1_item preference from web server: response 200 ok, value is: "+EntityUtils.toString(response1.getEntity())); + }else {flag2 = false; + logger.info("can not get the iris1_item from the web server");} + break; + } + } + } + catch (Exception e){ + e.printStackTrace(); + } + } + private void setNewValue(String[] values, String[] name, String file_typ){ + if(file_typ.equals("activity")) + { + int i = 0; + for(String value : values){ + String uri = ERASER_ITEM_URI + name[i] + "/state"; + try { + HttpResponse httpResponse = Request.Put(uri) + .bodyString(value, ContentType.TEXT_PLAIN) + .execute().returnResponse(); + String status=httpResponse.getStatusLine().toString(); + if(status.contains("200")){ + logger.info("put activity input name: "+name[i]+", value: "+value+"to web server: response 200 ok"); + }else{ + logger.info("can not put activity inputs to rest server"); + } + }catch (Exception e){ + e.printStackTrace(); + } + i+=1; + } + }else{ + String uri= ERASER_ITEM_URI + "w_brightness" +"/state"; + try { + HttpResponse httpResponse = Request.Put(uri) + .bodyString(values[1], ContentType.TEXT_PLAIN) + .execute().returnResponse(); + String put_response=httpResponse.getStatusLine().toString(); + if (put_response.contains("200")){logger.info("put w_brightness to web server: response 200 ok");}else{ + logger.info("can not put w_brightness to rest server"); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Main.java b/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..ac7536cae53749132be31ebe028e5ec0e55c30f4 --- /dev/null +++ b/benchmark/src/main/java/de/tudresden/inf/st/eraser/benchmark/Main.java @@ -0,0 +1,12 @@ +package de.tudresden.inf.st.eraser.benchmark; + +public class Main { + + public static void main(String[] args) { + String A_CSV_FILE_PATH = "../datasets/backup/activity_data.csv"; + String P_CSV_FILE_PATH = "../datasets/backup/preference_data.csv"; + Benchmark benchmark=new Benchmark(A_CSV_FILE_PATH,P_CSV_FILE_PATH); + benchmark.start(); + } +} + diff --git a/benchmark/src/main/resources/log4j2.xml b/benchmark/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c --- /dev/null +++ b/benchmark/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Console"> + <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> + </Console> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> + <Policies> + <OnStartupTriggeringPolicy/> + </Policies> + <DefaultRolloverStrategy max="20"/> + </RollingFile> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Root> + </Loggers> +</Configuration> diff --git a/build.gradle b/build.gradle index 7348f5134a49d4e0231a68894601c7c8684af1d6..f1f3b408f121f388615eca5d85cd3f128128f17c 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { allprojects { group = 'de.tudresden.inf.st' - version = '1.0.0-SNAPSHOT' + version = '0.1' } subprojects { @@ -31,4 +31,16 @@ subprojects { artifacts { testArtifacts testJar } + + repositories { + mavenCentral() + } + + dependencies { + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.2' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.2' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + } + } diff --git a/cloc/.gitignore b/cloc/.gitignore index b80f313c862d202eba757744403aaa366f6ba557..e76b2bf0f754f40018bff9aefac38201ee2a7c95 100644 --- a/cloc/.gitignore +++ b/cloc/.gitignore @@ -2,3 +2,4 @@ eraser-base.* eraser-base-* feedbackloop.txt my_definitions.txt +logs/ diff --git a/commons.color/.gitignore b/commons.color/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/commons.color/.gitignore +++ b/commons.color/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/commons.color/build.gradle b/commons.color/build.gradle index 135a0194474bfeab48418cd7589d117a4f360805..30ce209c40610e390f709d9ec5a36c7324543c23 100644 --- a/commons.color/build.gradle +++ b/commons.color/build.gradle @@ -1,18 +1,5 @@ -repositories { - mavenCentral() -} - -apply plugin: 'java' - -sourceCompatibility = 1.8 - dependencies { - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'org.apache.commons:commons-math3:3.6.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' } sourceSets { diff --git a/eraser-base/.gitignore b/eraser-base/.gitignore index a00ac8701e237817ed76681b9ea8db2e6229b818..19ad8f831544c2661c133c48b7da7a78df83a50d 100644 --- a/eraser-base/.gitignore +++ b/eraser-base/.gitignore @@ -3,3 +3,5 @@ src/gen/ src/gen-res/ .test-tmp /bin/ +logs/ +/doc/ diff --git a/eraser-base/build.gradle b/eraser-base/build.gradle index 3e5da9c2b15f01d65d652a3e99f4d45325777afe..fbe5f76ec7b7097ca4ef1ca48181b4c26eb71433 100644 --- a/eraser-base/build.gradle +++ b/eraser-base/build.gradle @@ -1,27 +1,24 @@ -repositories { - mavenCentral() -} - apply plugin: 'jastadd' apply plugin: 'application' apply plugin: 'jacoco' +apply plugin: 'idea' +apply plugin: 'distribution' dependencies { compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'org.fusesource.mqtt-client:mqtt-client:1.14' - compile 'org.apache.commons:commons-math3:3.6.1' - compile 'org.influxdb:influxdb-java:2.14' - testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' + compile group: 'org.influxdb', name: 'influxdb-java', version: '2.15' + testCompile group: 'org.testcontainers', name: 'testcontainers', version: '1.11.2' + testCompile group: 'org.testcontainers', name: 'influxdb', version: '1.11.2' + testCompile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' } buildscript { repositories.mavenLocal() repositories.mavenCentral() dependencies { - classpath 'org.jastadd:jastaddgradle:1.13.2' + classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.2' } } @@ -47,18 +44,22 @@ jacocoTestReport { } } +def relastFiles = fileTree('src/main/jastadd/') { + include '**/*.relast' }.toList().toArray() +String[] relastArguments = [ + "libs/relast.jar", + "--grammarName=./src/main/jastadd/mainGen", + "--useJastAddNames", + "--listClass=RefList", + "--jastAddList=JastAddList", + "--file" +] task preprocess(type: JavaExec) { group = 'Build' main = "-jar" - args = [ - "libs/relast-compiler.jar", - "./src/main/jastadd/main.relast", - "--listClass=RefList", - "--jastAddList=JastAddList", - "--file" - ] - - inputs.files file("./src/main/jastadd/main.relast") + args relastArguments + relastFiles + + inputs.files relastFiles outputs.files file("./src/main/jastadd/mainGen.ast"), file("./src/main/jastadd/mainGen.jadd") } @@ -79,6 +80,8 @@ jastadd { parser.genDir = "src/gen/java/de/tudresden/inf/st/eraser/jastadd/parser" } +idea.module.generatedSourceDirs += file('src/gen/java') + sourceSets.main { java { srcDir 'src/gen/java' @@ -90,32 +93,32 @@ javadoc { failOnError = false } -task RagDoll(type: Javadoc, dependsOn:javadoc) { - doFirst { - options.addStringOption("ragroot", "./src/main/jastadd") - } - group = "documentation" - description = 'create a RagDoll documentation' - classpath = javadoc.classpath - destinationDir = javadoc.destinationDir - excludes = javadoc.excludes - executable = javadoc.executable - failOnError = false - includes = javadoc.includes - options.doclet = "ragdoll.RagDollDoclet" - options.docletpath = files('libs/RagDoll.jar').asList() +String[] arguments = ["libs/rd-builder.jar", "-d", "doc/"] +def allSrcFiles = sourceSets.main.allSource.findAll { it.name.endsWith('java') }.toArray() +def ragdocViewSrcData = '../ragdoc-view/src/data/' - source = javadoc.source - options.linkSource = true +task ragdoc(type: JavaExec, dependsOn: assemble, overwrite: true) { + group = 'documentation' + description = 'Create ragdoc json documentation files' + main = "-jar" + args arguments + allSrcFiles +} - // title not working for some reason - title = "" - doLast { - println "Visit: file://" + destinationDir + "/index.html" - } +task cleanRagdoc(type: Delete) { + group = 'documentation' + delete fileTree(ragdocViewSrcData + '/*') +} + +task copyRagdoc(type: Copy, dependsOn: cleanRagdoc) { + group = 'documentation' + description = 'Copy ragdoc json documentation files to ragdoc-viewer' + from 'doc/' + into ragdocViewSrcData + eachFile { println it.file } } generateAst.dependsOn preprocess +generateAst.inputs.files file("./src/main/jastadd/mainGen.ast"), file("./src/main/jastadd/mainGen.jadd") //compileJava.dependsOn jastadd // //// always run jastadd diff --git a/eraser-base/libs/rd-builder.jar b/eraser-base/libs/rd-builder.jar new file mode 100644 index 0000000000000000000000000000000000000000..d5f8eed931b820b41f1fa22425b2833a4f90cd2c Binary files /dev/null and b/eraser-base/libs/rd-builder.jar differ diff --git a/eraser-base/libs/relast-compiler.jar b/eraser-base/libs/relast-compiler.jar deleted file mode 100644 index a1a9b656a4eb4f990decbcb8a522b063332133bf..0000000000000000000000000000000000000000 Binary files a/eraser-base/libs/relast-compiler.jar and /dev/null differ diff --git a/eraser-base/libs/relast.jar b/eraser-base/libs/relast.jar new file mode 100644 index 0000000000000000000000000000000000000000..9b52866a399cfdd0c4fe29c102125beab5d77989 Binary files /dev/null and b/eraser-base/libs/relast.jar differ diff --git a/eraser-base/src/main/jastadd/AdditionalTypes.jadd b/eraser-base/src/main/jastadd/AdditionalTypes.jadd index dbb1e695f544918c6e2019bcea5c32237ea0ec4d..32651117408970af5b3a57f4ddc48e2f7f2a0d8c 100644 --- a/eraser-base/src/main/jastadd/AdditionalTypes.jadd +++ b/eraser-base/src/main/jastadd/AdditionalTypes.jadd @@ -1,13 +1,29 @@ aspect AdditionalTypes { public class StringList extends beaver.Symbol implements Iterable<String> { - private java.util.List<String> delegatee = new java.util.ArrayList<String>(); + private java.util.Deque<String> delegatee = new java.util.ArrayDeque<>(); public java.util.Iterator<String> iterator() { - return delegatee.iterator(); + return delegatee.descendingIterator(); } public void add(String s) { delegatee.add(s); } } + + public class TypedKeyMap<T> extends beaver.Symbol implements Iterable<AbstractMap.SimpleEntry<T, String>> { + private java.util.Deque<AbstractMap.SimpleEntry<T, String>> delegatee = new java.util.ArrayDeque<>(); + + public java.util.Iterator<AbstractMap.SimpleEntry<T, String>> iterator() { + return delegatee.descendingIterator(); + } + + public void put(T key, String value) { + delegatee.add(new AbstractMap.SimpleEntry<>(key, value)); + } + } + + public class StringKeyMap extends TypedKeyMap<String> { } + + public class IntegerKeyMap extends TypedKeyMap<Integer> { } } diff --git a/eraser-base/src/main/jastadd/DecisionTree.jrag b/eraser-base/src/main/jastadd/DecisionTree.jrag index ed23d8ad22f1cb65acd34e2e5b2919571e788945..f50ca0562a920844e46a838c0f655f97bf804a01 100644 --- a/eraser-base/src/main/jastadd/DecisionTree.jrag +++ b/eraser-base/src/main/jastadd/DecisionTree.jrag @@ -4,26 +4,28 @@ aspect DecisionTree { public class DecisionTreeLeaf implements Leaf { } //--- classify --- - syn Leaf DecisionTreeRoot.classify() { + syn DecisionTreeLeaf DecisionTreeRoot.classify() { return getRootRule().classify(); } - syn Leaf DecisionTreeElement.classify(); + syn DecisionTreeLeaf DecisionTreeElement.classify(); - syn Leaf DecisionTreeRule.classify(); + syn DecisionTreeLeaf DecisionTreeRule.classify(); - syn Leaf ItemStateCheckRule.classify() { + syn DecisionTreeLeaf ItemStateCheckRule.classify() { boolean chooseLeft = getItemStateCheck().holds(); return (chooseLeft ? getLeft() : getRight()).classify(); } - syn Leaf DecisionTreeLeaf.classify() = this; + syn DecisionTreeLeaf DecisionTreeLeaf.classify() = this; //--- holds --- - syn boolean ItemStateCheck.holds(); + syn boolean ItemStateCheck.holds() = holdsFor(getItem()); - syn boolean ItemStateNumberCheck.holds() { - double actual = getItem().getStateAsDouble(); + //--- holdsFor --- + syn boolean ItemStateCheck.holdsFor(Item item); + eq ItemStateNumberCheck.holdsFor(Item item) { + double actual = item.getStateAsDouble(); double expected = getValue(); boolean result; switch (getComparator()) { @@ -38,12 +40,33 @@ aspect DecisionTree { return result; } - syn boolean ItemStateStringCheck.holds() { + eq ItemStateStringCheck.holdsFor(Item item) { // TODO implement this System.err.println("ItemStateStringCheck in Decision Tree not implemented!"); return false; } + //--- computePreferences --- + syn List<ItemPreference> DecisionTreeLeaf.computePreferences() { + // iterate over preference of this leaf, and all its parents and ancestors + List<ItemPreference> result = new ArrayList<>(); + Set<Item> seenItems = new HashSet<>(); + List<DecisionTreeElement> ancestors = ancestors(); + for (ItemPreference pref : getPreferenceList()) { + result.add(pref); + seenItems.add(pref.getItem()); + } + for (DecisionTreeElement ancestor : ancestors) { + for (ItemPreference pref : ancestor.getPreferenceList()) { + if (!seenItems.contains(pref.getItem())) { + result.add(pref); + seenItems.add(pref.getItem()); + } + } + } + return result; + } + //--- ancestors --- inh List<DecisionTreeElement> DecisionTreeElement.ancestors(); eq DecisionTreeRule.getLeft().ancestors() { @@ -57,5 +80,11 @@ aspect DecisionTree { return result; } eq DecisionTreeRoot.getRootRule().ancestors() = new ArrayList(); + eq DummyMachineLearningModel.getCurrent().ancestors() = Collections.emptyList(); + + public void DecisionTreeRoot.connectItems(List<String> itemNames) { + // TODO walk through the tree using depth-first-search + throw new UnsupportedOperationException(); + } } diff --git a/eraser-base/src/main/jastadd/DecisionTree.relast b/eraser-base/src/main/jastadd/DecisionTree.relast new file mode 100644 index 0000000000000000000000000000000000000000..17a1e65c1c6cd12ba2d49eeceba1eb920dae31bd --- /dev/null +++ b/eraser-base/src/main/jastadd/DecisionTree.relast @@ -0,0 +1,12 @@ +// ---------------- Decision Tree ------------------------------ +DecisionTreeRoot : InternalMachineLearningModel ::= RootRule:DecisionTreeRule ; +abstract DecisionTreeElement ::= Preference:ItemPreference*; +abstract DecisionTreeRule : DecisionTreeElement ::= Left:DecisionTreeElement Right:DecisionTreeElement <Label:String> ; +ItemStateCheckRule : DecisionTreeRule ::= ItemStateCheck ; + +abstract ItemStateCheck ::= <Comparator:ComparatorType> ; +rel ItemStateCheck.Item -> Item ; + +ItemStateNumberCheck : ItemStateCheck ::= <Value:double> ; +ItemStateStringCheck : ItemStateCheck ::= <Value:String> ; +DecisionTreeLeaf : DecisionTreeElement ::= <ActivityIdentifier:int> <Label:String> ; diff --git a/eraser-base/src/main/jastadd/Expression.jrag b/eraser-base/src/main/jastadd/Expression.jrag new file mode 100644 index 0000000000000000000000000000000000000000..e3d6a6b6b01effb2b014bbe85b7360aea5314a3a --- /dev/null +++ b/eraser-base/src/main/jastadd/Expression.jrag @@ -0,0 +1,33 @@ +aspect Expression { + + //--- eval (logical) --- + syn boolean LogicalExpression.eval(); + eq ParenthesizedLogicalExpression.eval() = getOperand().eval(); + eq NotExpression.eval() = !getOperand().eval(); + eq ComparingExpression.eval() { + double leftValue = getLeftOperand().eval(); + double rightValue = getRightOperand().eval(); + switch (getComparator()) { + case LessThan: return leftValue < rightValue; + case LessOrEqualThan: return leftValue <= rightValue; + case Equals: return leftValue == rightValue; + case NotEquals: return leftValue != rightValue; + case GreaterThan: return leftValue > rightValue; + case GreaterOrEqualThan: return leftValue >= rightValue; + } + return false; + } + eq AndExpression.eval() = getLeftOperand().eval() && getRightOperand().eval(); + eq OrExpression.eval() = getLeftOperand().eval() || getRightOperand().eval(); + + //--- eval (number) --- + syn double NumberExpression.eval(); + eq AddExpression.eval() = getLeftOperand().eval() + getRightOperand().eval(); + eq SubExpression.eval() = getLeftOperand().eval() - getRightOperand().eval(); + eq MultExpression.eval() = getLeftOperand().eval() * getRightOperand().eval(); + eq DivExpression.eval() = getLeftOperand().eval() / getRightOperand().eval(); + eq PowerExpression.eval() = Math.pow(getLeftOperand().eval(), getRightOperand().eval()); + eq Designator.eval() = getItem().getStateAsDouble(); + eq ParenthesizedNumberExpression.eval() = getOperand().eval(); + eq NumberLiteralExpression.eval() = getValue(); +} diff --git a/eraser-base/src/main/jastadd/Expression.relast b/eraser-base/src/main/jastadd/Expression.relast new file mode 100644 index 0000000000000000000000000000000000000000..591e112a21b904c88a450e3a2cf92c982a6a4c03 --- /dev/null +++ b/eraser-base/src/main/jastadd/Expression.relast @@ -0,0 +1,25 @@ +abstract Expression ; + +abstract NumberExpression : Expression ; +ParenthesizedNumberExpression : NumberExpression ::= Operand:NumberExpression ; +NumberLiteralExpression : NumberExpression ::= <Value:double> ; + +// variation point of the expression language: +Designator : NumberExpression ; +rel Designator.Item -> Item ; + +abstract BinaryNumberExpression : NumberExpression ::= LeftOperand:NumberExpression RightOperand:NumberExpression ; +AddExpression : BinaryNumberExpression ; +SubExpression : BinaryNumberExpression ; +MultExpression : BinaryNumberExpression ; +DivExpression : BinaryNumberExpression ; +PowerExpression : BinaryNumberExpression ; + +abstract LogicalExpression : Expression ; +ParenthesizedLogicalExpression : LogicalExpression ::= Operand:LogicalExpression ; +NotExpression : LogicalExpression ::= Operand:LogicalExpression ; +ComparingExpression : LogicalExpression ::= LeftOperand:NumberExpression RightOperand:NumberExpression <Comparator:ComparatorType> ; + +abstract BinaryLogicalExpression : LogicalExpression ::= LeftOperand:LogicalExpression RightOperand:LogicalExpression ; +AndExpression : BinaryLogicalExpression ; +OrExpression : BinaryLogicalExpression ; diff --git a/eraser-base/src/main/jastadd/Imports.jadd b/eraser-base/src/main/jastadd/Imports.jadd index 376d3aac516036dd8664b1870571f57688598120..319b024d1698c7efaabc89feed700f98fa24fa9c 100644 --- a/eraser-base/src/main/jastadd/Imports.jadd +++ b/eraser-base/src/main/jastadd/Imports.jadd @@ -1,5 +1,7 @@ import java.util.*; -import de.tudresden.inf.st.eraser.util.PrinterUtils; +import java.time.Instant; +import de.tudresden.inf.st.eraser.util.MemberPrinter; +import de.tudresden.inf.st.eraser.util.JavaUtils; aspect Imports { diff --git a/eraser-base/src/main/jastadd/Item.jrag b/eraser-base/src/main/jastadd/Item.jrag index 916dd0b786bbca7b3710244e55d631e0c2dd1a12..0b370142cf57c377775294bb6c758dafa7db036f 100644 --- a/eraser-base/src/main/jastadd/Item.jrag +++ b/eraser-base/src/main/jastadd/Item.jrag @@ -1,25 +1,39 @@ aspect ItemHandling { - private static final java.text.DateFormat DateTimeItem.FORMAT = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS") {{ - setTimeZone(TimeZone.getTimeZone("UTC")); - }}; + protected boolean Item.isFrozen = false; + public void Item.freeze() { isFrozen = true; } + public void Item.unfreeze() { isFrozen = false; } + public final boolean Item.isFrozen() { return isFrozen; } + + protected boolean Item.sendState = true; + public void Item.disableSendState() { sendState = false; } + public void Item.enableSendState() { sendState = true; } + public final boolean Item.isSendState() { return sendState; } + + //--- DateTimeItem.ALTERNATIVE_FORMAT --- + private static final java.time.format.DateTimeFormatter DateTimeItem.ALTERNATIVE_FORMAT = new java.time.format.DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .toFormatter() + .withZone(java.time.ZoneId.of("UTC")); //--- getStateAsString --- syn String Item.getStateAsString(); eq ColorItem.getStateAsString() = getState().toString(); - eq DateTimeItem.getStateAsString() = FORMAT.format(getState()); + eq DateTimeItem.getStateAsString() = getState().toString(); eq ItemWithBooleanState.getStateAsString() = Boolean.toString(getState()); + eq SwitchItem.getStateAsString() = getState() ? "ON" : "OFF"; + eq ContactItem.getStateAsString() = getState() ? "OPEN" : "CLOSED"; eq ItemWithDoubleState.getStateAsString() = Double.toString(getState()); - eq ItemWithStringState.getStateAsString() = getState().toString(); + eq ItemWithStringState.getStateAsString() = getState(); //--- getStateAsDouble --- syn double Item.getStateAsDouble(); // TupleHSB and String work like default eq ColorItem.getStateAsDouble() { logger.warn("getStateAsDouble called on item " + getLabel() + ". Using brightness."); - return getState().brightness; + return getState().getBrightness(); } - eq DateTimeItem.getStateAsDouble() = getState().getTime(); + eq DateTimeItem.getStateAsDouble() = getState().toEpochMilli(); eq ItemWithBooleanState.getStateAsDouble() = getState() ? 1 : 0; eq ItemWithDoubleState.getStateAsDouble() = getState(); eq ItemWithStringState.getStateAsDouble() { @@ -33,161 +47,274 @@ aspect ItemHandling { //--- setStateFromString --- public abstract void Item.setStateFromString(String value); - // TupleHSB public void ColorItem.setStateFromString(String value) { - this.setState(TupleHSB.parse(value)); + if (value.indexOf(",") == -1) { + // no commata, thus only one value, interpret as brightness + setBrightness(Integer.valueOf(value)); + } else { + // interpret as HSB value + setState(TupleHSB.parse(value)); + } } - // Date public void DateTimeItem.setStateFromString(String value) { - try{ - this.setState(FORMAT.parse(value)); - } catch (java.text.ParseException e1) { - // try to read input as number and use that - try { - long time = Long.parseLong(value); - this.setState(new Date(time)); - // exit the method to avoid printing the error message for e1 - return; - } catch (NumberFormatException e2) { - logger.catching(e2); - } - logger.catching(e1); + Exception exception = null; + // try normal instant parsing. Format example: 2019-07-22T12:58:08.960Z + try { + this.setState(Instant.parse(value)); + return; + } catch (java.time.format.DateTimeParseException e) { /* empty */ } + // try openHAB parsing. Format example: 2019-07-22T12:58:08.960+0000 + try { + this.setState(ALTERNATIVE_FORMAT.parse(value, Instant::from)); + return; + } catch (java.time.format.DateTimeParseException e) { /* empty */ } + // try to read input as number and use that + try { + long time = Long.parseLong(value); + this.setStateFromLong(time); + return; + } catch (NumberFormatException e) { + logger.warn("Could not parse date value from {}", value); } } - // boolean public void ItemWithBooleanState.setStateFromString(String value) { this.setState(Boolean.parseBoolean(value)); } - // double + public void SwitchItem.setStateFromString(String value) { + switch (value) { + case "ON": this.setState(true); break; + case "OFF": this.setState(false); break; + default: super.setStateFromString(value); + } + } + public void ContactItem.setStateFromString(String value) { + switch (value) { + case "OPEN": this.setState(true); break; + case "CLOSED": this.setState(false); break; + default: super.setStateFromString(value); + } + } + public void RollerShutterItem.setStateFromString(String value) { + switch (value) { + case "UP": this.setState(0); break; + case "DOWN": this.setState(100); break; + default: super.setStateFromString(value); + } + } public void ItemWithDoubleState.setStateFromString(String value) { this.setState(Double.parseDouble(value)); } - // String public void ItemWithStringState.setStateFromString(String value) { this.setState(value); } - public void Item.setStateFromString(String value, boolean shouldSendState) { - boolean before = getDefaultShouldSendState(); - setDefaultShouldSendState(shouldSendState); + boolean before = sendState; + sendState = shouldSendState; setStateFromString(value); - setDefaultShouldSendState(before); + sendState = before; } //--- setStateFromLong --- public abstract void Item.setStateFromLong(long value); - // TupleHSB public void ColorItem.setStateFromLong(long value) { // only set brightness - this.setState(TupleHSB.of(getState().hue, getState().saturation, Math.toIntExact(value))); + this.setState(getState().withDifferentBrightness(Math.toIntExact(value))); } - // Date public void DateTimeItem.setStateFromLong(long value) { - this.setState(new Date(value)); + this.setState(Instant.ofEpochMilli(value)); } - // boolean public void ItemWithBooleanState.setStateFromLong(long value) { this.setState(value != 0); } - // double public void ItemWithDoubleState.setStateFromLong(long value) { this.setState(value); } - // String public void ItemWithStringState.setStateFromLong(long value) { this.setState(Long.toString(value)); } - public void Item.setStateFromLong(long value, boolean shouldSendState) { - boolean before = getDefaultShouldSendState(); - setDefaultShouldSendState(shouldSendState); + boolean before = sendState; + sendState = shouldSendState; setStateFromLong(value); - setDefaultShouldSendState(before); + sendState = before; } -// public void setState(String value) { -// this.setState(value, getDefaultShouldSendState()); -// } + //--- setStateFromBoolean --- + public abstract void Item.setStateFromBoolean(boolean value); + public void ColorItem.setStateFromBoolean(boolean value) { + setBrightness(value ? 100 : 0); + } + public void DateTimeItem.setStateFromBoolean(boolean value) { + // there is no good way here + logger.warn("Ignoring boolean update using {} for {}", value, this); + } + public void ItemWithBooleanState.setStateFromBoolean(boolean value) { + this.setState(value); + } + public void ItemWithDoubleState.setStateFromBoolean(boolean value) { + this.setState(value ? 1.0 : 0.0); + } + public void ItemWithStringState.setStateFromBoolean(boolean value) { + this.setState(Boolean.toString(value)); + } + public void Item.setStateFromBoolean(boolean value, boolean shouldSendState) { + boolean before = sendState; + sendState = shouldSendState; + setStateFromBoolean(value); + sendState = before; + } -// public void Item.setState(String value, boolean shouldSendState) { -// // attention: call to super.setState() without shouldSendState -// this.setState(value); -// if (shouldSendState) { -// try { -// if (getTopic() != null) { -// sendState(); -// } -// } catch (Exception e) { -// logger.catching(e); -// } -// } -// if (hasObserver()) { -// getObserver().apply(); -// } -// } + //--- setStateFromDouble --- + public abstract void Item.setStateFromDouble(double value); + public void ColorItem.setStateFromDouble(double value) { + setBrightness((int) value); + } + public void DateTimeItem.setStateFromDouble(double value) { + this.setStateFromLong((long) value); + } + public void ItemWithBooleanState.setStateFromDouble(double value) { + this.setState(value != 0); + } + public void ItemWithDoubleState.setStateFromDouble(double value) { + this.setState(value); + } + public void ItemWithStringState.setStateFromDouble(double value) { + this.setState(Double.toString(value)); + } + public void Item.setStateFromDouble(double value, boolean shouldSendState) { + boolean before = sendState; + sendState = shouldSendState; + setStateFromDouble(value); + sendState = before; + } + + //--- setStateFromColor --- + public abstract void Item.setStateFromColor(TupleHSB value); + public void ColorItem.setStateFromColor(TupleHSB value) { + this.setState(value.clone()); + } + public void DateTimeItem.setStateFromColor(TupleHSB value) { + // there is no good way here + logger.warn("Ignoring color update using {} for {}", value, this); + } + public void ItemWithBooleanState.setStateFromColor(TupleHSB value) { + this.setState(value != null && value.getBrightness() > 0); + } + public void ItemWithDoubleState.setStateFromColor(TupleHSB value) { + this.setState(value.getBrightness()); + } + public void ItemWithStringState.setStateFromColor(TupleHSB value) { + this.setState(value.toString()); + } + public void Item.setStateFromColor(TupleHSB value, boolean shouldSendState) { + boolean before = sendState; + sendState = shouldSendState; + setStateFromColor(value); + sendState = before; + } + + //--- setStateFromInstant --- + public abstract void Item.setStateFromInstant(Instant value); + public void ColorItem.setStateFromInstant(Instant value) { + // there is no good way here + logger.warn("Ignoring date update using {} for {}", this, value); + } + public void DateTimeItem.setStateFromInstant(Instant value) { + this.setState(value); + } + public void ItemWithBooleanState.setStateFromInstant(Instant value) { + this.setState(value != null); + } + public void ItemWithDoubleState.setStateFromInstant(Instant value) { + this.setState(value.toEpochMilli()); + } + public void ItemWithStringState.setStateFromInstant(Instant value) { + this.setState(value.toString()); + } + public void Item.setStateFromInstant(Instant value, boolean shouldSendState) { + boolean before = sendState; + sendState = shouldSendState; + setStateFromInstant(value); + sendState = before; + } //--- getState --- public boolean ItemWithBooleanState.getState() { return get_state(); } public String ItemWithStringState.getState() { return get_state(); } public double ItemWithDoubleState.getState() { return get_state(); } public TupleHSB ColorItem.getState() { return get_state(); } - public Date DateTimeItem.getState() { return get_state(); } - - //--- copyState --- - public abstract Object Item.copyState(); - // boolean can be copied - public Boolean ItemWithBooleanState.copyState() { return get_state(); } - public String ItemWithStringState.copyState() { return new String(get_state()); } - // long can be copied - public Double ItemWithDoubleState.copyState() { return get_state(); } - public TupleHSB ColorItem.copyState() { return get_state().clone(); } - public Object DateTimeItem.copyState() { return get_state().clone(); } + public Instant DateTimeItem.getState() { return get_state(); } //--- setState(value) --- - public void ItemWithBooleanState.setState(boolean value) { setState(value, getDefaultShouldSendState()); } - public void ItemWithStringState.setState(String value) { setState(value, getDefaultShouldSendState()); } - public void ItemWithDoubleState.setState(double value) { setState(value, getDefaultShouldSendState()); } - public void ColorItem.setState(TupleHSB value) { setState(value, getDefaultShouldSendState()); } - public void DateTimeItem.setState(Date value) { setState(value, getDefaultShouldSendState()); } + public void ItemWithBooleanState.setState(boolean value) { setState(value, sendState); } + public void ItemWithStringState.setState(String value) { setState(value, sendState); } + public void ItemWithDoubleState.setState(double value) { setState(value, sendState); } + public void ColorItem.setState(TupleHSB value) { setState(value, sendState); } + public void DateTimeItem.setState(Instant value) { setState(value, sendState); } //--- setState(value,shouldSendState) --- public void ItemWithBooleanState.setState(boolean value, boolean shouldSendState) { + if (isFrozen || stateEquals(value)) { return; } set_state(value); - sendState0(shouldSendState); + stateUpdated(shouldSendState); } public void ItemWithStringState.setState(String value, boolean shouldSendState) { + if (isFrozen || stateEquals(value)) { return; } set_state(value); - sendState0(shouldSendState); + stateUpdated(shouldSendState); } public void ItemWithDoubleState.setState(double value, boolean shouldSendState) { + if (isFrozen || stateEquals(value)) { return; } set_state(value); - sendState0(shouldSendState); + stateUpdated(shouldSendState); } public void ColorItem.setState(TupleHSB value, boolean shouldSendState) { + if (isFrozen || stateEquals(value)) { return; } set_state(value); - sendState0(shouldSendState); + stateUpdated(shouldSendState); } - public void DateTimeItem.setState(Date value, boolean shouldSendState) { + public void DateTimeItem.setState(Instant value, boolean shouldSendState) { + if (isFrozen || stateEquals(value)) { return; } set_state(value); - sendState0(shouldSendState); + stateUpdated(shouldSendState); } - //--- sendState0 --- - protected void Item.sendState0(boolean shouldSendState) { + //--- stateUpdated --- + /** + * Called, whenever the state of an item is updated. Does various things including: + * <ul> + * <li>Send the new state via MQTT</li> + * <li>Send the new state to Influx DB</li> + * <li>Notify the attached {@link ItemObserver}, if any</li> + * <li>Update state of controlled items</li> + * </ul> + * @param shouldSendState whether to send the new state (currently affects MQTT and Influx) + */ + protected void Item.stateUpdated(boolean shouldSendState) { if (shouldSendState) { try { - // sendState() defined in MQTT aspect + // sendState() refined in MQTT and Influx aspect sendState(); } catch (Exception e) { logger.catching(e); } } - if (hasObserver()) { - getObserver().apply(); + if (hasItemObserver()) { + getItemObserver().apply(); + } + for (Item controlled : getControllingList()) { + doUpdateFor(controlled); + } + } + + //--- sendState --- + protected void Item.sendState() throws Exception { + for (MachineLearningModel model : getRelevantInMachineLearningModels()) { + model.getEncoder().newData(Collections.singletonList(this)); } } @@ -201,6 +328,9 @@ aspect ItemHandling { } public boolean ItemWithStringState.stateEquals(Object otherState) { + if (getState() == null) { + return otherState == null; + } return getState().equals(otherState); } @@ -212,18 +342,88 @@ aspect ItemHandling { } public boolean ColorItem.stateEquals(Object otherState) { + if (getState() == null) { + return otherState == null; + } return getState().equals(otherState); } public boolean DateTimeItem.stateEquals(Object otherState) { + if (getState() == null) { + return otherState == null; + } return getState().equals(otherState); } + //--- setStateToDefault --- public abstract void Item.setStateToDefault(); public void ColorItem.setStateToDefault() { this.setState(TupleHSB.of(0, 0, 0)); } - public void DateTimeItem.setStateToDefault() { this.setState(new Date(0)); } + public void DateTimeItem.setStateToDefault() { this.setState(Instant.ofEpochSecond(0)); } public void ItemWithBooleanState.setStateToDefault() { this.setState(false); } public void ItemWithDoubleState.setStateToDefault() { this.setState(0.0); } public void ItemWithStringState.setStateToDefault() { this.setState(""); } + //--- as$ItemType --- + // those attributes will raise a ClassCastException if called on the wrong item type. But what else can we do? + syn ColorItem Item.asColorItem() = (ColorItem) this; + syn ColorItem ColorItem.asColorItem() = this; + syn ItemWithBooleanState Item.asItemWithBooleanState() = (ItemWithBooleanState) this; + syn ItemWithBooleanState ItemWithBooleanState.asItemWithBooleanState() = this; + syn ItemWithStringState Item.asItemWithStringState() = (ItemWithStringState) this; + syn ItemWithStringState ItemWithStringState.asItemWithStringState() = this; + syn ItemWithDoubleState Item.asItemWithDoubleState() = (ItemWithDoubleState) this; + syn ItemWithDoubleState ItemWithDoubleState.asItemWithDoubleState() = this; + syn DateTimeItem Item.asDateTimeItem() = (DateTimeItem) this; + syn DateTimeItem DateTimeItem.asDateTimeItem() = this; + + //--- doUpdateFor --- + protected abstract void Item.doUpdateFor(Item controlling); + protected void ItemWithBooleanState.doUpdateFor(Item controlling) { + controlling.setStateFromBoolean(this.getState()); + } + protected void ItemWithStringState.doUpdateFor(Item controlling) { + controlling.setStateFromString(this.getState()); + } + protected void ItemWithDoubleState.doUpdateFor(Item controlling) { + controlling.setStateFromDouble(this.getState()); + } + protected void ColorItem.doUpdateFor(Item controlling) { + controlling.setStateFromColor(this.getState()); + } + protected void DateTimeItem.doUpdateFor(Item controlling) { + controlling.setStateFromInstant(this.getState()); + } + + private void ColorItem.setBrightness(int value) { + setState(getState().withDifferentBrightness(value)); + } + + //--- ItemPreference.apply --- + public abstract void ItemPreference.apply(); + public void ItemPreferenceColor.apply() { + logger.debug("Apply color preference {} -> {}", getItem().getID(), getPreferredHSB()); + getItem().setStateFromColor(getPreferredHSB()); + getItem().freeze(); + for (Item controller : getItem().getControlledByList()) { + controller.setStateFromColor(getPreferredHSB()); + } + getItem().unfreeze(); + } + public void ItemPreferenceDouble.apply() { + logger.debug("Apply double preference {} -> {}", getItem().getID(), getPreferredValue()); + getItem().setStateFromDouble(getPreferredValue()); + getItem().freeze(); + for (Item controller : getItem().getControlledByList()) { + controller.setStateFromDouble(getPreferredValue()); + } + getItem().unfreeze(); + } + +// // override Item.init$Children from JastAdd's own ASTNode aspect +// refine ASTNode public void Item.init$Children() { +// refined(); +// setDefaultShouldSendState(true); +// } + + } diff --git a/eraser-base/src/main/jastadd/ItemHistory.jrag b/eraser-base/src/main/jastadd/ItemHistory.jrag index aff6c228e6c2b14d3fb982997559b83e33abbae0..02f226f8d2a7c59ed7ad5d2d383609096af9d049 100644 --- a/eraser-base/src/main/jastadd/ItemHistory.jrag +++ b/eraser-base/src/main/jastadd/ItemHistory.jrag @@ -96,7 +96,7 @@ aspect ItemHistory { } // override Item.sendState from MQTT aspect - refine MQTT public void Item.sendState() throws Exception { + refine MQTT protected void Item.sendState() throws Exception { refined(); getRoot().getInfluxRoot().influxAdapter().write(pointFromState()); } diff --git a/eraser-base/src/main/jastadd/ItemHistoryPoints.jadd b/eraser-base/src/main/jastadd/ItemHistoryPoints.jadd index f349743611f46e326a5514ae0ba3418653c70fc0..dcb050d0c8e09887a0809689ddfaa0a854d1edef 100644 --- a/eraser-base/src/main/jastadd/ItemHistoryPoints.jadd +++ b/eraser-base/src/main/jastadd/ItemHistoryPoints.jadd @@ -8,8 +8,8 @@ aspect ItemHistory{ .build(); } protected abstract org.influxdb.dto.Point.Builder createMeasurement(); - public abstract java.time.Instant getTime(); - public abstract void setTime(java.time.Instant time); + public abstract Instant getTime(); + public abstract void setTime(Instant time); public abstract String getId(); public abstract void setId(String id); public abstract T getState(); @@ -23,18 +23,18 @@ aspect ItemHistory{ @org.influxdb.annotation.Measurement(name = BooleanStatePoint.NAME) public class BooleanStatePoint extends AbstractItemPoint<Boolean> { - @org.influxdb.annotation.Column(name = "time") protected java.time.Instant time; + @org.influxdb.annotation.Column(name = "time") protected Instant time; @org.influxdb.annotation.Column(name = "state") protected Boolean state; @org.influxdb.annotation.Column(name = "id", tag = true) protected String id; - public java.time.Instant getTime() { return time; } - public void setTime(java.time.Instant time) { this.time = time; } + public Instant getTime() { return time; } + public void setTime(Instant time) { this.time = time; } public String getId() { return id; } public void setId(String id) { this.id = id; } public void setState(Boolean state) { this.state = state; } public Boolean getState() { return state; } public static final String NAME = "ItemB"; - public static BooleanStatePoint of(java.time.Instant time, Boolean state, String id) { + public static BooleanStatePoint of(Instant time, Boolean state, String id) { BooleanStatePoint result = new BooleanStatePoint(); result.setTime(time); result.setState(state); result.setId(id); return result; @@ -46,18 +46,18 @@ aspect ItemHistory{ @org.influxdb.annotation.Measurement(name = StringStatePoint.NAME) public class StringStatePoint extends AbstractItemPoint<String> { - @org.influxdb.annotation.Column(name = "time") protected java.time.Instant time; + @org.influxdb.annotation.Column(name = "time") protected Instant time; @org.influxdb.annotation.Column(name = "state") protected String state; @org.influxdb.annotation.Column(name = "id", tag = true) protected String id; - public java.time.Instant getTime() { return time; } - public void setTime(java.time.Instant time) { this.time = time; } + public Instant getTime() { return time; } + public void setTime(Instant time) { this.time = time; } public String getId() { return id; } public void setId(String id) { this.id = id; } public void setState(String state) { this.state = state; } public String getState() { return state; } public static final String NAME = "ItemS"; - public static StringStatePoint of(java.time.Instant time, String state, String id) { + public static StringStatePoint of(Instant time, String state, String id) { StringStatePoint result = new StringStatePoint(); result.setTime(time); result.setState(state); result.setId(id); return result; @@ -69,18 +69,18 @@ aspect ItemHistory{ @org.influxdb.annotation.Measurement(name = DoubleStatePoint.NAME) public class DoubleStatePoint extends AbstractItemPoint<Double> { - @org.influxdb.annotation.Column(name = "time") protected java.time.Instant time; + @org.influxdb.annotation.Column(name = "time") protected Instant time; @org.influxdb.annotation.Column(name = "state") protected Double state; @org.influxdb.annotation.Column(name = "id", tag = true) protected String id; - public java.time.Instant getTime() { return time; } - public void setTime(java.time.Instant time) { this.time = time; } + public Instant getTime() { return time; } + public void setTime(Instant time) { this.time = time; } public String getId() { return id; } public void setId(String id) { this.id = id; } public void setState(Double state) { this.state = state; } public Double getState() { return state; } public static final String NAME = "ItemD"; - public static DoubleStatePoint of(java.time.Instant time, Double state, String id) { + public static DoubleStatePoint of(Instant time, Double state, String id) { DoubleStatePoint result = new DoubleStatePoint(); result.setTime(time); result.setState(state); result.setId(id); return result; @@ -92,18 +92,18 @@ aspect ItemHistory{ @org.influxdb.annotation.Measurement(name = ColorStatePoint.NAME) public class ColorStatePoint extends AbstractItemPoint<TupleHSB> { - @org.influxdb.annotation.Column(name = "time") protected java.time.Instant time; + @org.influxdb.annotation.Column(name = "time") protected Instant time; @org.influxdb.annotation.Column(name = "state") protected String state; @org.influxdb.annotation.Column(name = "id", tag = true) protected String id; - public java.time.Instant getTime() { return time; } - public void setTime(java.time.Instant time) { this.time = time; } + public Instant getTime() { return time; } + public void setTime(Instant time) { this.time = time; } public String getId() { return id; } public void setId(String id) { this.id = id; } public void setState(TupleHSB state) { this.state = state.toString(); } public TupleHSB getState() { return TupleHSB.parse(state); } public static final String NAME = "ItemC"; - public static ColorStatePoint of(java.time.Instant time, TupleHSB state, String id) { + public static ColorStatePoint of(Instant time, TupleHSB state, String id) { ColorStatePoint result = new ColorStatePoint(); result.setTime(time); result.setState(state); result.setId(id); return result; @@ -114,19 +114,19 @@ aspect ItemHistory{ } @org.influxdb.annotation.Measurement(name = DateTimeStatePoint.NAME) - public class DateTimeStatePoint extends AbstractItemPoint<Date> { - @org.influxdb.annotation.Column(name = "time") protected java.time.Instant time; + public class DateTimeStatePoint extends AbstractItemPoint<Instant> { + @org.influxdb.annotation.Column(name = "time") protected Instant time; @org.influxdb.annotation.Column(name = "state") protected long state; @org.influxdb.annotation.Column(name = "id", tag = true) protected String id; - public java.time.Instant getTime() { return time; } - public void setTime(java.time.Instant time) { this.time = time; } + public Instant getTime() { return time; } + public void setTime(Instant time) { this.time = time; } public String getId() { return id; } public void setId(String id) { this.id = id; } - public void setState(Date state) { this.state = state.getTime(); } - public Date getState() { return new Date(state); } + public void setState(Instant state) { this.state = state.toEpochMilli(); } + public Instant getState() { return Instant.ofEpochMilli(state); } public static final String NAME = "ItemT"; - public static DateTimeStatePoint of(java.time.Instant time, Date state, String id) { + public static DateTimeStatePoint of(Instant time, Instant state, String id) { DateTimeStatePoint result = new DateTimeStatePoint(); result.setTime(time); result.setState(state); result.setId(id); return result; diff --git a/eraser-base/src/main/jastadd/Location.jrag b/eraser-base/src/main/jastadd/Location.jrag new file mode 100644 index 0000000000000000000000000000000000000000..b61b260c998e15a89712e00f3e2c2a44eb84759b --- /dev/null +++ b/eraser-base/src/main/jastadd/Location.jrag @@ -0,0 +1,10 @@ +aspect Location { + syn Optional<Location> Item.myLocation() { + if (this.hasLocation()) { + return Optional.of(this.getLocation()); + } else { + return JavaUtils.ifPresentOrElseReturn(linkedThing(), + thing -> thing.hasLocation() ? Optional.of(thing.getLocation()) : Optional.empty(), () -> Optional.empty()); + } + } +} diff --git a/eraser-base/src/main/jastadd/Location.relast b/eraser-base/src/main/jastadd/Location.relast new file mode 100644 index 0000000000000000000000000000000000000000..3cdf199776adc6eaa575b31f867a16bae891e8d5 --- /dev/null +++ b/eraser-base/src/main/jastadd/Location.relast @@ -0,0 +1,3 @@ +Location ::= <Label:String> SubLocation:Location ; +rel Location.Thing* <-> Thing.Location? ; +rel Location.Item* <-> Item.Location? ; diff --git a/eraser-base/src/main/jastadd/Logging.jadd b/eraser-base/src/main/jastadd/Logging.jadd index 3fbe1ab40fdd10105f03ba1730e6c13e3b0ac399..710bd79a7bce048213ce0d52bb42cae5176b832a 100644 --- a/eraser-base/src/main/jastadd/Logging.jadd +++ b/eraser-base/src/main/jastadd/Logging.jadd @@ -1,6 +1,21 @@ aspect Logging { - private org.apache.logging.log4j.Logger MqttRoot.logger = org.apache.logging.log4j.LogManager.getLogger(MqttRoot.class); - private org.apache.logging.log4j.Logger Event.logger = org.apache.logging.log4j.LogManager.getLogger(Event.class); + // Base protected org.apache.logging.log4j.Logger Item.logger = org.apache.logging.log4j.LogManager.getLogger(Item.class); + protected org.apache.logging.log4j.Logger ItemPreference.logger = org.apache.logging.log4j.LogManager.getLogger(ItemPreference.class); + + // MachineLearning + private org.apache.logging.log4j.Logger DummyMachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(DummyMachineLearningModel.class); + private org.apache.logging.log4j.Logger InternalMachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(InternalMachineLearningModel.class); + private org.apache.logging.log4j.Logger ExternalMachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(ExternalMachineLearningModel.class); + + // NeuralNetwork + private org.apache.logging.log4j.Logger NeuralNetworkRoot.logger = org.apache.logging.log4j.LogManager.getLogger(NeuralNetworkRoot.class); + private org.apache.logging.log4j.Logger OutputLayer.logger = org.apache.logging.log4j.LogManager.getLogger(OutputLayer.class); protected org.apache.logging.log4j.Logger Neuron.logger = org.apache.logging.log4j.LogManager.getLogger(Neuron.class); + + // Rules + private org.apache.logging.log4j.Logger Rule.logger = org.apache.logging.log4j.LogManager.getLogger(Rule.class); + + // MQTT + private org.apache.logging.log4j.Logger MqttRoot.logger = org.apache.logging.log4j.LogManager.getLogger(MqttRoot.class); } diff --git a/eraser-base/src/main/jastadd/MachineLearning.jrag b/eraser-base/src/main/jastadd/MachineLearning.jrag index 6a69f5c89a60695d10ae9ec0554ca37f827abf17..e584d8f2f6b9f1b2b0ce140a46c005b32887e1dd 100644 --- a/eraser-base/src/main/jastadd/MachineLearning.jrag +++ b/eraser-base/src/main/jastadd/MachineLearning.jrag @@ -1,41 +1,193 @@ aspect MachineLearning { + public static final MachineLearningRoot MachineLearningRoot.createDefault() { + MachineLearningRoot result = new MachineLearningRoot(); + return result; + } + public interface Leaf { String getLabel(); int getActivityIdentifier(); List<ItemPreference> computePreferences(); } - syn Leaf MachineLearningModel.classify(); + syn Leaf InternalMachineLearningModel.classify(); //--- currentActivityName --- - syn String Root.currentActivityName() = getMachineLearningRoot().hasActivityRecognition() ? getMachineLearningRoot().getActivityRecognition().classify().getLabel() : "no activity"; + syn String Root.currentActivityName() = JavaUtils.ifPresentOrElseReturn( + currentActivity(), + Activity::getLabel, + () -> "no activity" + ); //--- currentActivity --- - syn java.util.Optional<Activity> Root.currentActivity() = resolveActivity(getMachineLearningRoot().hasActivityRecognition() ? getMachineLearningRoot().getActivityRecognition().classify().getActivityIdentifier() : -1); + syn java.util.Optional<Activity> Root.currentActivity() { + return resolveActivity((int) getOpenHAB2Model().getActivityItem().getState()); + } + private int Root.extractActivityIdentifier(List<ItemPreference> preferences) { + if (preferences.isEmpty()) { + return -1; + } + return (int) ((ItemPreferenceDouble) preferences.get(0)).getPreferredValue(); + } //--- currentPreferences --- - syn List<ItemPreference> Root.currentPreferences() = getMachineLearningRoot().getPreferenceLearning().classify().computePreferences(); - - //--- computePreferences --- - syn List<ItemPreference> DecisionTreeLeaf.computePreferences() { - // iterate over preference of this leaf, and all its parents and ancestors - List<ItemPreference> result = new ArrayList<>(); - Set<Item> seenItems = new HashSet<>(); - List<DecisionTreeElement> ancestors = ancestors(); - for (ItemPreference pref : getPreferenceList()) { - result.add(pref); - seenItems.add(pref.getItem()); + syn List<ItemPreference> Root.currentPreferences() = getMachineLearningRoot().getPreferenceLearning().getDecoder().classify().getPreferences(); + + //--- canSetActivity --- + syn boolean MachineLearningModel.canSetActivity() = false; + eq DummyMachineLearningModel.canSetActivity() = true; + + //--- setActivity --- + public void MachineLearningModel.setActivity(int activityIdentifier) { /* empty by default */ } + public void DummyMachineLearningModel.setActivity(int activityIdentifier) { + getCurrent().setActivityIdentifier(activityIdentifier); + } + + //--- DummyMachineLearningModel.classify --- + eq DummyMachineLearningModel.classify() { + if (logger.isInfoEnabled() && getItemList().size() > 0) { + logger.info("Dummy classification of {}, values of connected items: {}", + mlKind(), + getItemList().stream() + .map(item -> item.getID() + ":" + item.getStateAsString()) + .collect(java.util.stream.Collectors.toList())); } - for (DecisionTreeElement ancestor : ancestors) { - for (ItemPreference pref : ancestor.getPreferenceList()) { - if (!seenItems.contains(pref.getItem())) { - result.add(pref); - seenItems.add(pref.getItem()); - } + return getCurrent(); + } + + //--- DummyMachineLearningModel.createDefault() --- + public static DummyMachineLearningModel DummyMachineLearningModel.createDefault() { + DummyMachineLearningModel dummy = new DummyMachineLearningModel(); + DecisionTreeLeaf current = new DecisionTreeLeaf(); + current.setActivityIdentifier(0); + current.setLabel("Dummy"); + // no item preference set + dummy.setCurrent(current); + return dummy; + } + + //--- connectItems --- + public abstract void MachineLearningModel.connectItems(List<String> itemNames); + public void DummyMachineLearningModel.connectItems(List<String> itemNames) { + logger.info("Storing items to connect"); + for (String itemName : itemNames) { + JavaUtils.ifPresentOrElse(getRoot().getOpenHAB2Model().resolveItem(itemName), + this::addItem, + () -> logger.warn("Could not resolve item '{}'", itemName)); + } + } + + //--- check --- + /** + * Checks the ML model for all necessary children. + * @return true, if everything is alright. false otherwise + */ + public abstract boolean MachineLearningModel.check(); + + @Override + public boolean InternalMachineLearningModel.check() { + boolean good = true; + if (getOutputApplication() == null) { + logger.warn("{}: OutputApplication function is null!", mlKind()); + good = false; + } + return good; + } + + @Override + public boolean DummyMachineLearningModel.check() { + // ignore definition of MachineLearningModel, thus ignore output application + boolean good = true; + if (getCurrent() == null) { + logger.error("{}: Current is null!", mlKind()); + good = false; + } else { + if (getCurrent().getNumPreference() == 0) { + logger.warn("{}: No item preferences set.", mlKind()); } } + return good; + } + + @Override + public boolean ExternalMachineLearningModel.check() { + logger.warn("check not available for external ML models (yet)!"); + return true; + } + + //--- mlKind --- + inh String MachineLearningModel.mlKind(); + eq MachineLearningRoot.getActivityRecognition().mlKind() = "ActivityRecognition"; + eq MachineLearningRoot.getPreferenceLearning().mlKind() = "PreferenceLearning"; + + //... ExternalMachineLearningModel ... + private MachineLearningEncoder ExternalMachineLearningModel.encoder; + public void ExternalMachineLearningModel.setEncoder(MachineLearningEncoder encoder) { + this.encoder = encoder; + } + private MachineLearningDecoder ExternalMachineLearningModel.decoder; + public void ExternalMachineLearningModel.setDecoder(MachineLearningDecoder decoder) { + this.decoder = decoder; + } +// eq ExternalMachineLearningModel.classify() = null; + public void ExternalMachineLearningModel.connectItems(List<String> itemNames) { } + + //... InternalMachineLearningModel ... + syn InternalMachineLearningHandler InternalMachineLearningModel.handler() { + return new InternalMachineLearningHandler().setModel(this); + } + cache InternalMachineLearningModel.handler(); + + //--- getEncoder --- + public abstract MachineLearningEncoder MachineLearningModel.getEncoder(); + @Override + public MachineLearningEncoder InternalMachineLearningModel.getEncoder() { + return handler(); + } + @Override + public MachineLearningEncoder ExternalMachineLearningModel.getEncoder() { + return this.encoder; + } + + //--- getDecoder --- + public abstract MachineLearningDecoder MachineLearningModel.getDecoder(); + @Override + public MachineLearningDecoder InternalMachineLearningModel.getDecoder() { + return handler(); + } + @Override + public MachineLearningDecoder ExternalMachineLearningModel.getDecoder() { + return this.decoder; + } + +} + +aspect ChangeEvents { + private static final java.util.concurrent.atomic.AtomicInteger ChangeEvent.idCounter = new java.util.concurrent.atomic.AtomicInteger(0); + + //--- createRecognitionEvent --- + public static RecognitionEvent RecognitionEvent.createRecognitionEvent(MachineLearningModel modelOfRecognition) { + RecognitionEvent result = new RecognitionEvent(); + result.initChangeEvent(); + for (Item relevantItem : modelOfRecognition.getRelevantItems()) { + result.addChangedItem(ChangedItem.newFromItem(relevantItem)); + } return result; } + //--- newFromItem --- + public static ChangedItem ChangedItem.newFromItem(Item source) { + ChangedItem result = new ChangedItem(); + result.setItem(source); + result.setNewStateAsString(source.getStateAsString()); + return result; + } + + //--- initChangeEvent --- + protected void ChangeEvent.initChangeEvent() { + this.setCreated(Instant.now()); + this.setIdentifier(idCounter.incrementAndGet()); + } + } diff --git a/eraser-base/src/main/jastadd/MachineLearning.relast b/eraser-base/src/main/jastadd/MachineLearning.relast new file mode 100644 index 0000000000000000000000000000000000000000..906d344b8f123fca5d617b174078dcfa56f7f4c8 --- /dev/null +++ b/eraser-base/src/main/jastadd/MachineLearning.relast @@ -0,0 +1,28 @@ +// ---------------- Machine Learning Model ------------------------------ +MachineLearningRoot ::= [ActivityRecognition:MachineLearningModel] [PreferenceLearning:MachineLearningModel] Activity* ChangeEvent* ; + +Activity ::= <Identifier:int> <Label:String> ; + +abstract ChangeEvent ::= <Identifier:int> <Created:Instant> ChangedItem* ; + +ChangedItem ::= <NewStateAsString:String> ; +rel ChangedItem.Item -> Item ; + +RecognitionEvent : ChangeEvent ; +rel RecognitionEvent.Activity -> Activity ; + +ManualChangeEvent : ChangeEvent ; + +abstract MachineLearningModel ::= ; +rel MachineLearningModel.RelevantItem* <-> Item.RelevantInMachineLearningModel* ; +rel MachineLearningModel.TargetItem* <-> Item.TargetInMachineLearningModel* ; + +ExternalMachineLearningModel : MachineLearningModel ; + +abstract InternalMachineLearningModel : MachineLearningModel ::= <OutputApplication:DoubleDoubleFunction> ; + +abstract ItemPreference ::= ; +rel ItemPreference.Item -> Item ; + +ItemPreferenceColor : ItemPreference ::= <PreferredHSB:TupleHSB> ; +ItemPreferenceDouble : ItemPreference ::= <PreferredValue:double> ; diff --git a/eraser-base/src/main/jastadd/ModelStatistics.jrag b/eraser-base/src/main/jastadd/ModelStatistics.jrag index 293fcd920a69bbdf3393add6fd62c451d0abe87a..443635f1e2f3ceb71785e5977d43ba27f55c2b6c 100644 --- a/eraser-base/src/main/jastadd/ModelStatistics.jrag +++ b/eraser-base/src/main/jastadd/ModelStatistics.jrag @@ -1,7 +1,7 @@ aspect ModelStatistics { //--- numChannels --- - syn int Root.numChannels() { + syn int OpenHAB2Model.numChannels() { int sum = 0; for (Thing thing : getThingList()) { sum += thing.getNumChannel(); @@ -10,7 +10,7 @@ aspect ModelStatistics { } //--- description --- - syn String Root.description() = "[" + syn String OpenHAB2Model.description() = "[" + this.getNumThingType() + " thing type(s), " + this.getNumChannelType() + " channel type(s), " + this.numChannels() + " channel(s), " diff --git a/eraser-base/src/main/jastadd/Navigation.jrag b/eraser-base/src/main/jastadd/Navigation.jrag index b1025c8092e3a0989dca556f1dcbd855c30f64f9..69592c1bf0b980b6287cb28d10b7b131bd4383ef 100644 --- a/eraser-base/src/main/jastadd/Navigation.jrag +++ b/eraser-base/src/main/jastadd/Navigation.jrag @@ -1,36 +1,36 @@ aspect Navigation { - syn Comparator<ModelElement> Root.modelElementComparator() { + syn Comparator<ModelElement> OpenHAB2Model.modelElementComparator() { return (e1, e2) -> (e1.getID().compareTo(e2.getID())); } //--- items --- - syn java.util.List<Item> Root.items() { + syn java.util.List<Item> OpenHAB2Model.items() { java.util.List<Item> result = new java.util.ArrayList<>(); addItems(result, getGroupList()); return result; } - private void Root.addItems(java.util.List<Item> result, JastAddList<Group> groups) { + private void OpenHAB2Model.addItems(java.util.List<Item> result, JastAddList<Group> groups) { groups.forEach(group -> group.getItemList().forEach(item -> result.add(item))); } //--- parameters --- - syn java.util.Set<Parameter> Root.parameters() { + syn java.util.Set<Parameter> OpenHAB2Model.parameters() { java.util.Set<Parameter> result = new java.util.TreeSet<>(modelElementComparator()); getThingTypeList().forEach(tt -> tt.getParameterList().forEach(parameter -> result.add(parameter))); return result; } //--- channels --- - syn java.util.Set<Channel> Root.channels() { + syn java.util.Set<Channel> OpenHAB2Model.channels() { java.util.Set<Channel> result = new java.util.TreeSet<>(modelElementComparator()); getThingList().forEach(thing -> thing.getChannelList().forEach(channel -> result.add(channel))); return result; } //--- resolveThingType --- - syn java.util.Optional<ThingType> Root.resolveThingType(String thingTypeId) { + syn java.util.Optional<ThingType> OpenHAB2Model.resolveThingType(String thingTypeId) { for (ThingType thingType : this.getThingTypeList()) { if (thingType.getID().equals(thingTypeId)) { return java.util.Optional.of(thingType); @@ -40,7 +40,7 @@ aspect Navigation { } //--- resolveChannel --- - syn java.util.Optional<Channel> Root.resolveChannel(String channelId) { + syn java.util.Optional<Channel> OpenHAB2Model.resolveChannel(String channelId) { for (Thing thing : this.getThingList()) { for (Channel channel : thing.getChannelList()) { if (channel.getID().equals(channelId)) { @@ -52,7 +52,7 @@ aspect Navigation { } //--- resolveChannelType --- - syn java.util.Optional<ChannelType> Root.resolveChannelType(String channelTypeId) { + syn java.util.Optional<ChannelType> OpenHAB2Model.resolveChannelType(String channelTypeId) { for (ChannelType channelType : this.getChannelTypeList()) { if (channelType.getID().equals(channelTypeId)) { return java.util.Optional.of(channelType); @@ -62,7 +62,10 @@ aspect Navigation { } //--- resolveItem --- - syn java.util.Optional<Item> Root.resolveItem(String itemId) { + syn java.util.Optional<Item> OpenHAB2Model.resolveItem(String itemId) { + if ("activity".equals(itemId)) { + return Optional.of(getActivityItem()); + } for (Item item : items()) { if (item.getID().equals(itemId)) { return java.util.Optional.of(item); @@ -72,7 +75,7 @@ aspect Navigation { } //--- resolveGroup --- - syn java.util.Optional<Group> Root.resolveGroup(String groupId) { + syn java.util.Optional<Group> OpenHAB2Model.resolveGroup(String groupId) { for (Group group : this.getGroupList()) { if (group.getID().equals(groupId)) { return java.util.Optional.of(group); @@ -87,7 +90,7 @@ aspect Navigation { } //--- resolveItemCategory --- - syn java.util.Optional<ItemCategory> Root.resolveItemCategory(String categoryName) { + syn java.util.Optional<ItemCategory> OpenHAB2Model.resolveItemCategory(String categoryName) { for (ItemCategory category : getItemCategoryList()) { if (category.getName().equals(categoryName)) { return java.util.Optional.of(category); @@ -116,26 +119,31 @@ aspect Navigation { return java.util.Optional.empty(); } - //--- containingChannel --- - inh Channel Link.containingChannel(); - eq Channel.getLink().containingChannel() = this; - //--- containingThing --- inh Thing Channel.containingThing(); eq Thing.getChannel().containingThing() = this; + //--- containingNeuralNetwork --- + inh NeuralNetworkRoot OutputLayer.containingNeuralNetwork(); + eq NeuralNetworkRoot.getOutputLayer().containingNeuralNetwork() = this; + + //--- linkedThing --- + syn Optional<Thing> Item.linkedThing() { + if (!this.hasChannel()) { + return Optional.empty(); + } + Channel channel = this.getChannel(); + Thing thing = channel.containingThing(); + return Optional.of(thing); + } + //--- getRoot --- inh Root ASTNode.getRoot(); - eq Root.getChannelCategory().getRoot() = this; + eq Root.getOpenHAB2Model().getRoot() = this; eq Root.getMqttRoot().getRoot() = this; eq Root.getInfluxRoot().getRoot() = this; eq Root.getMachineLearningRoot().getRoot() = this; - eq Root.getItemObserver().getRoot() = this; - eq Root.getThing().getRoot() = this; - eq Root.getGroup().getRoot() = this; - eq Root.getThingType().getRoot() = this; - eq Root.getChannelType().getRoot() = this; eq Root.getRule().getRoot() = this; - eq Root.getItemCategory().getRoot() = this; eq Root.getUser().getRoot() = this; + eq Root.getLocation().getRoot() = this; } diff --git a/eraser-base/src/main/jastadd/NeuralNetwork.jrag b/eraser-base/src/main/jastadd/NeuralNetwork.jrag index c075af0c8d50be1842ad340cc2063dd8e49b3ab5..e673b69a1ea95c386320630fae1c8152cfe9cf78 100644 --- a/eraser-base/src/main/jastadd/NeuralNetwork.jrag +++ b/eraser-base/src/main/jastadd/NeuralNetwork.jrag @@ -4,11 +4,13 @@ aspect NeuralNetwork { // let any number be a Leaf public class DoubleNumber implements Leaf { public final double number; - DoubleNumber(double number) { + public final Item affectedItem; + DoubleNumber(Item affectedItem, double number) { + this.affectedItem = affectedItem; this.number = number; } - public static DoubleNumber of(double number) { - return new DoubleNumber(number); + public static DoubleNumber of(Item affectedItem, double number) { + return new DoubleNumber(affectedItem, number); } public String getLabel() { return Double.toString(this.number); @@ -17,29 +19,33 @@ aspect NeuralNetwork { return (int) number; } public List<ItemPreference> computePreferences() { - return new ArrayList<>(); + return Collections.singletonList(new ItemPreferenceDouble(affectedItem, number)); } } + //--- classify --- syn DoubleNumber NeuralNetworkRoot.classify() { return getOutputLayer().classify(); } -// class FormulaEvaluator { -// public static double evaluate(String formula, double[] inputs) { -// -// } -// } - syn DoubleNumber OutputLayer.classify() { double[] inputs = new double[getNumOutputNeuron()]; for (int i = 0; i < getNumOutputNeuron(); ++i) { OutputNeuron n = getOutputNeuron(i); inputs[i] = n.value(); } - return DoubleNumber.of(getCombinator().apply(inputs)); + return DoubleNumber.of(getAffectedItem(), containingNeuralNetwork().getOutputApplication().apply(getCombinator().apply(inputs))); } + //--- createEmpty --- + public static NeuralNetworkRoot NeuralNetworkRoot.createEmpty() { + NeuralNetworkRoot result = new NeuralNetworkRoot(); + // use identity function + result.setOutputApplication(d -> d); + return result; + } + + //--- getLabel (not used) --- syn String OutputLayer.getLabel() { StringBuilder sb = new StringBuilder(); for (OutputNeuron n : getOutputNeuronList()) { @@ -48,26 +54,32 @@ aspect NeuralNetwork { return sb.toString(); } + //--- value --- syn double Neuron.value(); - syn double HiddenNeuron.value() { + eq HiddenNeuron.value() { double[] inputs = new double[getInputs().size()]; for (int i=0; i<inputs.length; ++i) { NeuronConnection connection = getInputList().get(i); inputs[i] = connection.containingNeuron().value() * connection.getWeight(); +// logger.debug("{}[{}]: {} * {}", this, i, connection.containingNeuron().value(), connection.getWeight()); } double result = getActivationFormula().apply(inputs); - logger.debug("{}: {} -> {}", this, java.util.Arrays.toString(inputs), result); +// logger.debug("{}: {} -> {}", this, java.util.Arrays.toString(inputs), result); return result; - } + } + + eq BiasNeuron.value() = 1; - syn double InputNeuron.value() { + eq InputNeuron.value() { return getItem().getStateAsDouble(); } + //--- containingNeuron --- inh Neuron NeuronConnection.containingNeuron(); eq Neuron.getOutput().containingNeuron() = this; + //--- connectTo --- public void Neuron.connectTo(Neuron otherNeuron, double weight) { NeuronConnection connection = new NeuronConnection(); connection.setWeight(weight); @@ -75,4 +87,94 @@ aspect NeuralNetwork { this.addOutput(connection); } + //--- connectItems --- + public void NeuralNetworkRoot.connectItems(List<String> itemNames) { + int numItems = itemNames.size(); + if (numItems > getNumInputNeuron()) { + logger.warn("Excessive item names (total: {}) specfied to connect {} input neurons.", numItems, getNumInputNeuron()); + } + for (int i = 0; i < getNumInputNeuron(); ++i) { + if (i >= numItems) { + logger.error("Not enough item names specified to connect all input neurons."); + return; + } + String itemName = itemNames.get(i); + InputNeuron neuron = getInputNeuron(i); + JavaUtils.ifPresentOrElse(getRoot().getOpenHAB2Model().resolveItem(itemName), + neuron::setItem, + () -> logger.warn("Could not resolve item '{}'", itemName)); + } + } + + //--- check --- + @Override + public boolean NeuralNetworkRoot.check() { + boolean good = super.check(); + if (getNumInputNeuron() == 0) { + logger.warn("{}: There are no input neurons.", mlKind()); + } + if (getNumHiddenNeuron() == 0) { + logger.warn("{}: There are no hidden neurons.", mlKind()); + } + for (InputNeuron inputNeuron : getInputNeuronList()) { + good &= inputNeuron.check(); + } + for (HiddenNeuron hiddenNeuron : getHiddenNeuronList()) { + good &= hiddenNeuron.check(); + } + good &= getOutputLayer().check(); + return good; + } + + public boolean OutputLayer.check() { + boolean good = true; + if (getCombinator() == null) { + logger.error("{}: There is no combinator function.", mlKind()); + good = false; + } + for (OutputNeuron outputNeuron : getOutputNeuronList()) { + good &= outputNeuron.check(); + } + return good; + } + + public boolean Neuron.check() { + for (NeuronConnection outputConnection : getOutputList()) { + if (outputConnection.getWeight() == 0) { + logger.warn("{}: Connection from {} to {} has weight zero.", mlKind(), outputConnection.getNeuron(), this); + } + } + return true; + } + + @Override + public boolean InputNeuron.check() { + boolean good = super.check(); + if (getItem() == null) { + logger.error("{}: No item connected to {}.", mlKind(), this); + return false; + } + return good; + } + + @Override + public boolean HiddenNeuron.check() { + boolean good = super.check(); + if (getActivationFormula() == null) { + logger.error("{}: Neuron {} has no activation function.", mlKind(), this); + good = false; + } + return good; + } + + @Override + public boolean BiasNeuron.check() { + setActivationFormula(inputs -> 1.0); + return super.check(); + } + + //--- mlKind --- + inh String OutputLayer.mlKind(); + inh String Neuron.mlKind(); + } diff --git a/eraser-base/src/main/jastadd/NeuralNetwork.relast b/eraser-base/src/main/jastadd/NeuralNetwork.relast new file mode 100644 index 0000000000000000000000000000000000000000..1214eb199e7ae39e9bae4754eabe6ee0fcd14ae3 --- /dev/null +++ b/eraser-base/src/main/jastadd/NeuralNetwork.relast @@ -0,0 +1,20 @@ +// ---------------- Neural Network ------------------------------ +NeuralNetworkRoot : InternalMachineLearningModel ::= InputNeuron* HiddenNeuron* OutputLayer ; + +OutputLayer ::= OutputNeuron* <Combinator:DoubleArrayDoubleFunction> ; +rel OutputLayer.AffectedItem -> Item ; + +abstract Neuron ::= Output:NeuronConnection* ; + +NeuronConnection ::= <Weight:double> ; +rel NeuronConnection.Neuron <-> Neuron.Input* ; + +InputNeuron : Neuron ; +rel InputNeuron.Item -> Item ; + +HiddenNeuron : Neuron ::= <ActivationFormula:DoubleArrayDoubleFunction> ; +BiasNeuron : HiddenNeuron ; +OutputNeuron : HiddenNeuron ::= <Label:String> ; + +DummyMachineLearningModel : InternalMachineLearningModel ::= Current:DecisionTreeLeaf ; +rel DummyMachineLearningModel.Item* -> Item ; diff --git a/eraser-base/src/main/jastadd/Printing.jrag b/eraser-base/src/main/jastadd/Printing.jrag index bda979a24f7aff3a2b3e6bfecf6003754acdc7a4..7402973f563eb21924f1f54a54ae8dc6d2bea848 100644 --- a/eraser-base/src/main/jastadd/Printing.jrag +++ b/eraser-base/src/main/jastadd/Printing.jrag @@ -3,8 +3,17 @@ aspect Printing { String ASTNode.safeID(ModelElement elem) { return elem == null ? "NULL" : elem.getID(); } -//Root ::= Thing* Item* Group* ThingType* ChannelType* MqttRoot ; syn String Root.prettyPrint() { + StringBuilder sb = new StringBuilder(); + sb.append(getOpenHAB2Model().prettyPrint()); + sb.append(getMqttRoot().prettyPrint()); + sb.append(getInfluxRoot().prettyPrint()); + sb.append(getMachineLearningRoot().prettyPrint()); + return sb.toString(); + } + +//--- OpenHAB2Model.prettyPrint() --- + syn String OpenHAB2Model.prettyPrint() { StringBuilder sb = new StringBuilder(); for (Thing t : getThingList()) { sb.append(t.prettyPrint()); @@ -27,71 +36,74 @@ aspect Printing { for (Channel c : channels()) { sb.append(c.prettyPrint()); } - sb.append(getMqttRoot().prettyPrint()); - sb.append(getInfluxRoot().prettyPrint()); return sb.toString(); } -//Thing: id="id" label="label" type="type" channels=[]; +//Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ; syn String Thing.prettyPrint() { - StringBuilder sb = new StringBuilder("Thing: id=\""); - sb.append(getID()) - .append("\" label=\"").append(getLabel()) - .append("\" type=\"").append(safeID(getType())); - PrinterUtils.concatIds(sb.append("\" channels=["), getChannelList()); - return sb.append("] ;\n").toString(); + return new MemberPrinter("Thing") + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addRequired("type", getType(), ThingType::getID) + .addIds("channels", getNumChannel(), getChannelList()) + .build(); } -//Item: id="id" label="label" state="state" topic="topic"; +//ITEM_TYPE Item: id="" label="" state="" category="" topic="" controls=["ITEM_ID", "ITEM_ID"]; syn String Item.prettyPrint() { - StringBuilder sb = new StringBuilder(prettyPrintType()); - sb.append("Item: id=\"").append(getID()); - if (getLabel() != null && !getLabel().isEmpty()) { - sb.append("\" label=\"").append(getLabel()); - } - sb.append("\" state=\"").append(getStateAsString()); - if (hasCategory()) { - sb.append("\" category=\"").append(getCategory().getName()); - } - if (getTopic() != null) { - sb.append("\" topic=\"").append(getTopic().allParts()); - } - return sb.append("\" ;\n").toString(); + return new MemberPrinter(prettyPrintType()) + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addRequired("state", getStateAsString()) + .addOptional("category", hasCategory(), () -> getCategory().getName()) + .addOptional("topic", hasTopic(), () -> getTopic().getTopicString()) + .addIds("controls", getControllingList()) + .addNodes("metaData", getNumMetaData(), getMetaDataList(), + md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"", + MemberPrinter.ListBracketType.CURLY) + .build(); } syn String Item.prettyPrintType(); - eq ColorItem.prettyPrintType() = "Color " ; - eq ContactItem.prettyPrintType() = "Contact " ; - eq DateTimeItem.prettyPrintType() = "DateTime " ; - eq DimmerItem.prettyPrintType() = "Dimmer " ; - eq ImageItem.prettyPrintType() = "Image " ; - eq LocationItem.prettyPrintType() = "Location " ; - eq NumberItem.prettyPrintType() = "Number " ; - eq PlayerItem.prettyPrintType() = "Player " ; - eq RollerShutterItem.prettyPrintType() = "RollerShutter " ; - eq StringItem.prettyPrintType() = "String " ; - eq SwitchItem.prettyPrintType() = "Switch " ; - eq DefaultItem.prettyPrintType() = "" ; - -//Group: id="id" [label="label"] [aggregation="name"] groups=[] items=[]; -//Group: id="id" [label="label"] [aggregation="name" ("p1", "p2")] groups=[] items=[]; + eq ColorItem.prettyPrintType() = "Color Item" ; + eq ContactItem.prettyPrintType() = "Contact Item" ; + eq DateTimeItem.prettyPrintType() = "DateTime Item" ; + eq DimmerItem.prettyPrintType() = "Dimmer Item" ; + eq ImageItem.prettyPrintType() = "Image Item" ; + eq LocationItem.prettyPrintType() = "Location Item" ; + eq NumberItem.prettyPrintType() = "Number Item" ; + eq PlayerItem.prettyPrintType() = "Player Item" ; + eq RollerShutterItem.prettyPrintType() = "RollerShutter Item" ; + eq StringItem.prettyPrintType() = "String Item" ; + eq SwitchItem.prettyPrintType() = "Switch Item" ; + eq ActivityItem.prettyPrintType() = "Activity Item" ; + eq DefaultItem.prettyPrintType() = "Item" ; + + // special ActivityItem printing. Always omit state. + syn String ActivityItem.prettyPrint() { + return new MemberPrinter(prettyPrintType()) + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addOptional("category", hasCategory(), () -> getCategory().getName()) + .addOptional("topic", hasTopic(), () -> getTopic().getTopicString()) + .addIds("controls", getControllingList()) + .addNodes("metaData", getNumMetaData(), getMetaDataList(), + md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"", + MemberPrinter.ListBracketType.CURLY) + .build(); + } + + +//Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation=AGG; +// AGG either '"agg-name"', or '"agg-name" ("param1", "param2")' syn String Group.prettyPrint() { - StringBuilder sb = new StringBuilder("Group: id=\"").append(getID()).append("\""); - if (getLabel() != null && !getLabel().isEmpty()) { - sb.append(" label=\"").append(getLabel()).append("\""); - } - if (getAggregationFunction() != null) { - sb.append(getAggregationFunction().prettyPrint()); - } - if (getNumGroup() > 0) { - PrinterUtils.concatIds(sb.append(" groups=["), getGroups()); - sb.append("]"); - } - if (getNumItem() > 0) { - PrinterUtils.concatIds(sb.append(" items=["), getItems()); - sb.append("]"); - } - return sb.append(" ;\n").toString(); + return new MemberPrinter("Group") + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addIds("groups", getNumGroup(), getGroups()) + .addIds("items", getNumItem(), getItems()) + .addOptionalPrettyPrint(getAggregationFunction()) + .build(); } syn String GroupAggregationFunction.prettyPrint(); @@ -109,66 +121,53 @@ aspect Printing { return sb.toString(); } -//ThingType: id="id" label="label" description="desc" parameters=[] channelTypes=[]; +//ThingType: id="" label="" description="" parameters=["PARAM_ID", "PARAM_ID"] channelTypes=["CHANNEL_TYPE_ID", "CHANNEL_TYPE_ID"]; syn String ThingType.prettyPrint() { - StringBuilder sb = new StringBuilder("ThingType: id=\""); - sb.append(getID()) - .append("\" label=\"").append(getLabel()) - .append("\" description=\"").append(getDescription()); - PrinterUtils.concatIds(sb.append("\" parameters=["), getParameters()); - PrinterUtils.concatIds(sb.append("] channelTypes=["), getChannelTypes()); - return sb.append("] ;\n").toString(); + return new MemberPrinter("ThingType") + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addNonDefault("description", getDescription()) + .addIds("parameters", getNumParameter(), getParameters()) + .addIds("channelTypes", getChannelTypes()) + .build(); } -//Parameter: id="id" label="" description="" type="" context="" default="" required; -//Parameter: id="id" label="" description="" type="" context="" ; +//Parameter: id="" label="" description="" type="" default="" required; syn String Parameter.prettyPrint() { - StringBuilder sb = new StringBuilder("Parameter: id=\""); - sb.append(getID()) - .append("\" label=\"").append(getLabel()) - .append("\" description=\"").append(getDescription()) - .append("\" type=\"").append(getType()) - .append("\" context=\"").append(getContext()).append("\""); - if (hasDefaultValue()) { - sb.append(" default=\"").append(getDefaultValue().getValue()).append("\""); - } - if (getRequired()) { - sb.append(" required"); - } - return sb.append(" ;\n").toString(); + return new MemberPrinter("Parameter") + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addNonDefault("description", getDescription()) + .addOptional("type", getType() != null, () -> getType().toString()) + .addNonDefault("context", getContext()) + .addOptional("default", hasDefaultValue(), () -> getDefaultValue().getValue()) + .addFlag("required", getRequired()) + .build(); } -//ChannelType: id="id" label="label" description="desc" itemType="type" category=(default TEXT) readyOnly; -//ChannelType: id="id" label="label" description="desc" itemType="type" category=(simple "") readyOnly; +//ChannelType: id="" label="" description="" itemType="" category="" readyOnly; syn String ChannelType.prettyPrint() { - StringBuilder sb = new StringBuilder("ChannelType: id=\""); - sb.append(getID()) - .append("\" label=\"").append(getLabel()) - .append("\" description=\"").append(getDescription()).append("\""); - if (getItemType() != null){ - sb.append(" itemType=\"").append(getItemType().name()).append("\""); - } - if (getChannelCategory() != null) { - sb.append(" category=\"").append(getChannelCategory().prettyPrint()).append("\""); - } - if (getReadOnly()) { - sb.append(" readOnly"); - } - return sb.append(" ;\n").toString(); + return new MemberPrinter("ChannelType") + .addRequired("id", getID()) + .addNonDefault("label", getLabel()) + .addNonDefault("description", getDescription()) + .addOptional("itemType", getItemType() != null, () -> getItemType().name()) + .addOptional("category", getChannelCategory() != null, () -> getChannelCategory().prettyPrint()) + .addFlag("readOnly", getReadOnly()) + .build(); } syn String DefaultChannelCategory.prettyPrint() = getValue().name(); syn String SimpleChannelCategory.prettyPrint() = getValue(); -//Channel: id="id" type="" links=["ITEM_ID", "ITEM_ID"]; +//Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"]; syn String Channel.prettyPrint() { - StringBuilder sb = new StringBuilder("Channel: id=\""); - sb.append(getID()) - .append("\" type=\"").append(safeID(getType())) - .append("\" links=["); - return PrinterUtils.concatIds(sb, getLinkList(), Link::getItem) - .append("] ;\n").toString(); + return new MemberPrinter("Channel") + .addRequired("id", getID()) + .addRequired("type", getType(), ChannelType::getID) + .addIds("links", getLinkedItems()) + .build(); } //ExternalHost: "hostName:port" @@ -180,37 +179,57 @@ aspect Printing { return getHostName() + ":" + getPort(); } -//Mqtt: incoming="oh2/out" outgoing="oh2/in" host=""; +//Mqtt: incoming="" outgoing="" host=""; syn String MqttRoot.prettyPrint() { - StringBuilder sb = new StringBuilder("Mqtt: incoming=\""); - sb.append(getIncomingPrefix()) - .append("\" outgoing=\"").append(getOutgoingPrefix()); - if (this.hasHost()) { - sb.append("\" host=\"").append(getHost().prettyPrint(DEFAULT_PORT)); - } - return sb.append("\" ;\n").toString(); + return new MemberPrinter("Mqtt") + .addNonDefault("incoming", getIncomingPrefix()) + .addNonDefault("outgoing", getOutgoingPrefix()) + .addOptional("host", hasHost(), () -> getHost().prettyPrint(DEFAULT_PORT)) + .build(); } -//Influx: host=""; +//Influx: user="" password="" dbName="" host="" ; syn String InfluxRoot.prettyPrint() { - StringBuilder sb = new StringBuilder("Influx:"); - if (nonDefault(this.getUser(), DEFAULT_USER)) { - sb.append(" user=\"").append(getUser()).append("\""); - } - if (nonDefault(this.getPassword(), DEFAULT_PASSWORD)) { - sb.append(" password=\"").append(getPassword()).append("\""); - } - if (nonDefault(this.getDbName(), DEFAULT_DB_NAME)) { - sb.append(" dbName=\"").append(getDbName()).append("\""); - } - if (this.hasHost()) { - sb.append(" host=\"").append(getHost().prettyPrint(DEFAULT_PORT)).append("\""); - } - return sb.append(" ;\n").toString(); + return new MemberPrinter("Influx") + .addNonDefault("user", getUser(), DEFAULT_USER) + .addNonDefault("password", getPassword(), DEFAULT_PASSWORD) + .addNonDefault("dbName", getDbName(), DEFAULT_DB_NAME) + .addOptional("host", hasHost(), () -> getHost().prettyPrint(DEFAULT_PORT)) + .build(); } - private boolean InfluxRoot.nonDefault(String actual, String expected) { - return actual != null && !actual.equals(expected); +// Activities: { index: "name" } + syn String MachineLearningRoot.prettyPrint() { + return new MemberPrinter("ML") + .addNodes("activities", getNumActivity(), getActivityList(), + activity -> activity.getIdentifier() + ":\"" + activity.getLabel() + "\"", + MemberPrinter.ListBracketType.CURLY) + .build(); + } + +// Expressions + syn String ParenthesizedNumberExpression.prettyPrint() = "(" + getOperand().prettyPrint() + ")"; + syn String NumberLiteralExpression.prettyPrint() = Double.toString(getValue()); + syn String AddExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " + " + getRightOperand().prettyPrint() + ")"; + syn String SubExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " - " + getRightOperand().prettyPrint() + ")"; + syn String MultExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " * " + getRightOperand().prettyPrint() + ")"; + syn String DivExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " / " + getRightOperand().prettyPrint() + ")"; + syn String PowerExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " ^ " + getRightOperand().prettyPrint() + ")"; + syn String ParenthesizedLogicalExpression.prettyPrint() = "(" + getOperand().prettyPrint() + ")"; + syn String NotExpression.prettyPrint() = "!" + getOperand().prettyPrint(); + syn String 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()); + } } + syn String AndExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " & " + getRightOperand().prettyPrint() + ")"; + syn String OrExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " | " + getRightOperand().prettyPrint() + ")"; + syn String Designator.prettyPrint() = getItem().getID(); } diff --git a/eraser-base/src/main/jastadd/Rules.jrag b/eraser-base/src/main/jastadd/Rules.jrag index 96ec84feceef82622f37e495a2bd7e06959169ff..e1e0db7c7248016dd6db7bea0d6579994548cee9 100644 --- a/eraser-base/src/main/jastadd/Rules.jrag +++ b/eraser-base/src/main/jastadd/Rules.jrag @@ -1,102 +1,116 @@ aspect Rules { - Object ItemObserver.last_known_state = null; - - // idea: abuse dependency tracking to trigger rule only iff item state has changed - syn boolean ItemObserver.apply() { -// if (getObservedItem().getStateAsString() != last_known_state) { - if (!getObservedItem().stateEquals(last_known_state)) { - // state has changed - last_known_state = getObservedItem().copyState(); - for (Event event : getTriggeredEventList()) { - // TODO maybe trigger those events asynchronously? - event.triggerRule(); - } - return true; + public void ItemObserver.apply() { + // state has changed, so trigger rules + for (Rule rule : getTriggeredRuleList()) { + rule.trigger(observedItem()); } - return false; } - // TODO this should maybe happen automatically, but setState has to be changed for this - syn boolean Root.applyRules() { - boolean atLeastOneRuleApplied = false; - for (Rule rule : getRuleList()) { - rule.apply(); - atLeastOneRuleApplied = true; + public void Rule.trigger(Item triggeringItem) { + // 1) check conditions + for (Condition condition : getConditionList()) { + if (!condition.holdsFor(triggeringItem)) { + return; + } + } + // 2) execute actions + for (Action action : getActionList()) { + action.applyFor(triggeringItem); } - return atLeastOneRuleApplied; } - syn boolean Rule.apply() { - // iterate over events, and "try" to apply them. - // if one succeeds, then stop, since we want to avoid firing the rule multiple times - for (Event event : getEventList()) { - if (event.apply()) { - return true; + inh Item ItemObserver.observedItem(); + eq Item.getItemObserver().observedItem() = this; + + public void Rule.activateFor(Item item) { + // 1) Get or create new ItemObserver, and add it to Root + ItemObserver itemObserver; + if (item.hasItemObserver()) { + itemObserver = item.getItemObserver(); + // 1.a) Check if observer already triggers this rule + for (Rule rule : itemObserver.getTriggeredRules()) { + if (rule.equals(this)) { + logger.warn("Rule already activated for item {}. Ignoring.", item); + return; + } } + } else { + itemObserver = new ItemObserver(); + item.setItemObserver(itemObserver); } - return false; - } - - syn boolean Event.apply() { - return getObserver().apply(); + // 2) Link event and itemObserver + itemObserver.addTriggeredRule(this); } - inh Rule Event.containingRule(); - eq Rule.getEvent().containingRule() = this; + private static java.util.concurrent.ScheduledExecutorService Rule.executor = java.util.concurrent.Executors.newScheduledThreadPool(4); - public void Event.triggerRule(){ - containingRule().trigger(); + /** + * Trigger this rule in the given period with no initial delay. + * @param period the period between successive executions + * @param unit the time unit of the period parameter + * @return a ScheduledFuture that can be used to cancel + */ + public java.util.concurrent.ScheduledFuture Rule.activateEvery(long period, java.util.concurrent.TimeUnit unit) { + return activateEvery(0, period, unit); } - public void Rule.trigger() { - // 1) check condition - Root model = getRoot(); - if (getCondition().apply(model)) { - // 2) execute action - getAction().accept(model); - } + /** + * Trigger this rule in the given period with the given initial delay. + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @param unit the time unit of the initialDelay and period parameters + * @return a ScheduledFuture that can be used to cancel + */ + public java.util.concurrent.ScheduledFuture Rule.activateEvery(long initialDelay, long period, java.util.concurrent.TimeUnit unit) { + return executor.scheduleAtFixedRate(() -> trigger(null), initialDelay, period, unit); } - public void Event.activateFor(Item item) { - // 0) Check if this event has already an observer (which will be lost) - if (getObserver() != null) { - logger.warn("Overriding observer in {}", this.getLabel()); - } - // 1) Get or create new ItemObserver, and add it to Root - ItemObserver itemObserver; - if (item.hasObserver()) { - itemObserver = item.getObserver(); + public void Rule.removeActivationOf(Item item) { + if (item.hasItemObserver()) { + item.getItemObserver().removeTriggeredRule(this); } else { - itemObserver = new ItemObserver(); - getRoot().addItemObserver(itemObserver); - item.setObserver(itemObserver); - itemObserver.last_known_state = item.copyState(); + // there is no observer yet + logger.warn("Item {} was never activated before.", item); } - // 2) Link event and itemObserver - itemObserver.addTriggeredEvent(this); } - public void Rule.addEventFor(Item item, String label) { - Event e = new Event(label); - this.addEvent(e); - e.activateFor(item); - } + // --- Condition.holdsFor --- + syn boolean Condition.holdsFor(Item item); + eq ItemStateCheckCondition.holdsFor(Item item) = getItemStateCheck().holdsFor(item); + eq ExpressionCondition.holdsFor(Item item) = getLogicalExpression().eval(); - public boolean Event.removeSelf() { - // 1) Remove from ItemObserver - getObserver().removeTriggeredEvent(this); - // 2) Remove from Root - if (getParent() == null) { - return false; - } - for (int i = 0; i < getParent().numChildren(); ++i) { - if (getParent().getChild(i) == this) { - getParent().removeChild(i); - return true; - } - } - return false; + // --- Action.applyFor --- + public abstract void Action.applyFor(Item item); + 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) { + 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.doUpdateFor(getAffectedItem()); + } + public void SetStateFromItemsAction.applyFor(Item item) { + getAffectedItem().setStateFromString(getCombinator().apply(getSourceItems())); + } + public void AddDoubleToStateAction.applyFor(Item item) { + getAffectedItem().setStateFromDouble(getAffectedItem().getStateAsDouble() + getIncrement()); + } + public void MultiplyDoubleToStateAction.applyFor(Item item) { + getAffectedItem().setStateFromDouble(getAffectedItem().getStateAsDouble() * getMultiplier()); } } diff --git a/eraser-base/src/main/jastadd/Rules.relast b/eraser-base/src/main/jastadd/Rules.relast new file mode 100644 index 0000000000000000000000000000000000000000..3385858454a006cda3c052dbc41028684cb17d23 --- /dev/null +++ b/eraser-base/src/main/jastadd/Rules.relast @@ -0,0 +1,29 @@ +// --- New ECA rules --- +Rule ::= Condition* Action* ; +abstract Condition ; +ItemStateCheckCondition : Condition ::= ItemStateCheck ; +ExpressionCondition : Condition ::= LogicalExpression ; +abstract Action ; +NoopAction : Action ; +LambdaAction : Action ::= <Lambda:Action2EditConsumer> ; + +TriggerRuleAction : Action ; +rel TriggerRuleAction.Rule -> Rule ; + +abstract SetStateAction : Action ; +rel SetStateAction.AffectedItem -> Item ; + +SetStateFromExpression : 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> ; + +ItemObserver ::= ; +rel ItemObserver.TriggeredRule* <-> Rule.Observer* ; diff --git a/eraser-base/src/main/jastadd/Util.jrag b/eraser-base/src/main/jastadd/Util.jrag index b7e252afd51fda93273eba63e745483f7c78df59..b038ca0dfae51d7bbea748086965b78e40f0c3e6 100644 --- a/eraser-base/src/main/jastadd/Util.jrag +++ b/eraser-base/src/main/jastadd/Util.jrag @@ -5,19 +5,32 @@ aspect Util { // return new ExternalHost(hostName, 1883); // } public void MqttRoot.setHostByName(String hostName) { - setHost(new ExternalHost(hostName, 1883)); + setHost(ExternalHost.of(hostName, DEFAULT_PORT)); + flushCache(); } public void InfluxRoot.setHostByName(String hostName) { - setHost(new ExternalHost(hostName, 8086)); + setHost(ExternalHost.of(hostName, DEFAULT_PORT)); + } + + public static ExternalHost ExternalHost.of(String hostName, int defaultPort) { + String host = hostName; + int port = defaultPort; + if (hostName.contains(":")) { + String[] parts = hostName.split(":"); + host = parts[0]; + port = Integer.parseInt(parts[1]); + } + return new ExternalHost(host, port); } syn String ExternalHost.urlAsString() = String.format("http://%s:%s", getHostName(), getPort()); public static Root Root.createEmptyRoot() { Root model = new Root(); + model.setOpenHAB2Model(new OpenHAB2Model()); model.setMqttRoot(new MqttRoot()); - model.setInfluxRoot(new InfluxRoot()); + model.setInfluxRoot(InfluxRoot.createDefault()); model.setMachineLearningRoot(new MachineLearningRoot()); return model; } diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex index 8ffefb73b6c6dd219dd4a59839fed77e8302ea47..bc9b3e8d9a41c9bd4cc51d1879f081b32538f103 100644 --- a/eraser-base/src/main/jastadd/eraser.flex +++ b/eraser-base/src/main/jastadd/eraser.flex @@ -30,11 +30,11 @@ import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser.Terminals; %} WhiteSpace = [ ] | \t | \f | \n | \r | \r\n -//Identifier = [:jletter:][:jletterdigit:]* +Identifier = [:jletter:][:jletterdigit:]* Text = \" ([^\"]*) \" -//Integer = [:digit:]+ // | "+" [:digit:]+ | "-" [:digit:]+ -//Real = [:digit:]+ "." [:digit:]* | "." [:digit:]+ +Integer = [:digit:]+ // | "+" [:digit:]+ | "-" [:digit:]+ +Real = [:digit:]+ "." [:digit:]* | "." [:digit:]+ Comment = "//" [^\n\r]+ @@ -55,7 +55,10 @@ Comment = "//" [^\n\r]+ "Channel" { return sym(Terminals.CHANNEL); } "Mqtt" { return sym(Terminals.MQTT); } "Influx" { return sym(Terminals.INFLUX); } +"ML" { return sym(Terminals.ML); } +"Rule" { return sym(Terminals.RULE); } // special items (group already has a token definition) +"Activity" { return sym(Terminals.ACTIVITY); } "Color" { return sym(Terminals.COLOR); } "Contact" { return sym(Terminals.CONTACT); } "DateTime" { return sym(Terminals.DATE_TIME); } @@ -68,11 +71,13 @@ Comment = "//" [^\n\r]+ "String" { return sym(Terminals.STRING); } "Switch" { return sym(Terminals.SWITCH); } // within specification +"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); } "context" { return sym(Terminals.CONTEXT); } +"controls" { return sym(Terminals.CONTROLS); } "dbName" { return sym(Terminals.DB_NAME); } "default" { return sym(Terminals.DEFAULT); } "description" { return sym(Terminals.DESCRIPTION); } @@ -84,6 +89,7 @@ Comment = "//" [^\n\r]+ "itemType" { return sym(Terminals.ITEM_TYPE); } "label" { return sym(Terminals.LABEL); } "links" { return sym(Terminals.LINKS); } +"metaData" { return sym(Terminals.META_DATA); } "outgoing" { return sym(Terminals.OUTGOING); } "parameters" { return sym(Terminals.PARAMETERS); } "password" { return sym(Terminals.PASSWORD); } @@ -96,6 +102,20 @@ Comment = "//" [^\n\r]+ // special characters "=" { return sym(Terminals.EQUALS); } //"\"" { return sym(Terminals.QUOTE); } +"<" { return sym(Terminals.LT); } +"<=" { return sym(Terminals.LE); } +"==" { return sym(Terminals.EQ); } +"!=" { return sym(Terminals.NE); } +">=" { return sym(Terminals.GE); } +">" { return sym(Terminals.GT); } +"+" { return sym(Terminals.PLUS); } +"*" { return sym(Terminals.MULT); } +"-" { return sym(Terminals.MINUS); } +"/" { return sym(Terminals.DIV); } +"^" { return sym(Terminals.POW); } +"!" { return sym(Terminals.EXCLAMATION); } +"|" { return sym(Terminals.OR); } +"&" { return sym(Terminals.AND); } ":" { return sym(Terminals.COLON); } "," { return sym(Terminals.COMMA); } ";" { return sym(Terminals.SEMICOLON); } @@ -103,8 +123,12 @@ Comment = "//" [^\n\r]+ "]" { return sym(Terminals.RB_SQUARE); } "(" { return sym(Terminals.LB_ROUND); } ")" { return sym(Terminals.RB_ROUND); } -//{Identifier} { return sym(Terminals.NAME); } +"{" { return sym(Terminals.LB_CURLY); } +"}" { return sym(Terminals.RB_CURLY); } +{Identifier} { return sym(Terminals.NAME); } {Text} { return symText(Terminals.TEXT); } -//{Real} { return sym(Terminals.REAL); } -//{Integer} { return sym(Terminals.INTEGER); } +{Integer} { return sym(Terminals.INTEGER); } +{Real} { return sym(Terminals.REAL); } <<EOF>> { return sym(Terminals.EOF); } +/* error fallback */ +[^] { throw new Error("Illegal character '"+ yytext() +"' at line " + (yyline+1) + " column " + (yycolumn+1)); } diff --git a/eraser-base/src/main/jastadd/eraser.parser b/eraser-base/src/main/jastadd/eraser.parser index bc065f7338ee33d4e641b3b494b6e57a539c4660..9934302f129072e5165966d34a4300281312fdbd 100644 --- a/eraser-base/src/main/jastadd/eraser.parser +++ b/eraser-base/src/main/jastadd/eraser.parser @@ -1,6 +1,7 @@ %header {: package de.tudresden.inf.st.eraser.jastadd.parser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.jastadd.model.Action; import de.tudresden.inf.st.eraser.parser.EraserParserHelper; import java.util.Map; import java.util.HashMap; @@ -23,31 +24,80 @@ import java.util.HashMap; :} ; %goal goal; +%goal number_expression; +%goal logical_expression; Root goal = - thing.t goal.r {: insertZero(r.getThingList(), t); return r; :} - | item.i goal.r {: return r; :} - | group.g goal.r {: insertZero(r.getGroupList(), g); return r; :} - | thing_type.tt goal.r {: insertZero(r.getThingTypeList(), tt); return r; :} - | parameter goal.r {: return r; :} - | channel_type.ct goal.r {: insertZero(r.getChannelTypeList(), ct); return r; :} - | channel.c goal.r {: return r; :} - | mqtt_root.mr goal.r {: r.setMqttRoot(mr); return r; :} - | thing.t {: return eph.createRoot(t); :} - | item.i {: return eph.createRoot(); :} - | group.g {: return eph.createRoot(g); :} - | thing_type.tt {: return eph.createRoot(tt); :} - | parameter {: return eph.createRoot(); :} - | channel_type.ct {: return eph.createRoot(ct); :} - | mqtt_root.mr {: return eph.createRoot(mr); :} - | influx_root.ir {: return eph.createRoot(ir); :} - | channel.c {: return eph.createRoot(); :} + thing.t goal.r {: insertZero(r.getOpenHAB2Model().getThingList(), t); return r; :} + | item.i goal.r {: return r; :} + | group.g goal.r {: insertZero(r.getOpenHAB2Model().getGroupList(), g); return r; :} + | thing_type.tt goal.r {: insertZero(r.getOpenHAB2Model().getThingTypeList(), tt); return r; :} + | parameter goal.r {: return r; :} + | channel_type.ct goal.r {: insertZero(r.getOpenHAB2Model().getChannelTypeList(), ct); return r; :} + | channel.c goal.r {: return r; :} + | mqtt_root.mr goal.r {: r.setMqttRoot(mr); return r; :} + | 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; :} + | thing.t {: return eph.createRoot(t); :} + | item.i {: return eph.createRoot(); :} + | group.g {: return eph.createRoot(g); :} + | thing_type.tt {: return eph.createRoot(tt); :} + | parameter {: return eph.createRoot(); :} + | channel_type.ct {: return eph.createRoot(ct); :} + | channel.c {: return eph.createRoot(); :} + | mqtt_root.mr {: return eph.createRoot(mr); :} + | influx_root.ir {: return eph.createRoot(ir); :} + | machine_learning_root.ml {: return eph.createRoot(ml); :} + | rule.rule {: return eph.createRoot(rule); :} + ; + +%left RB_ROUND; +%left MULT, DIV; +%left PLUS, MINUS; +%left POW; +%left LT, LE, EQ, GE, GT; +%left OR; +%left AND; + +NumberExpression number_expression = + LB_ROUND number_expression.a MULT number_expression.b RB_ROUND {: return new MultExpression(a, b); :} + | LB_ROUND number_expression.a DIV number_expression.b RB_ROUND {: return new DivExpression(a, b); :} + | LB_ROUND number_expression.a PLUS number_expression.b RB_ROUND {: return new AddExpression(a, b); :} + | LB_ROUND number_expression.a MINUS number_expression.b RB_ROUND {: return new SubExpression(a, b); :} + | LB_ROUND number_expression.a POW number_expression.b RB_ROUND {: return new PowerExpression(a, b); :} + | literal_expression.l {: return l; :} + | designator.d {: return d; :} + | LB_ROUND number_expression.e RB_ROUND {: return new ParenthesizedNumberExpression(e); :} + ; + +LogicalExpression logical_expression = + LB_ROUND logical_expression.a AND logical_expression.b RB_ROUND {: return new AndExpression(a, b); :} + | 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); :} + ; + +NumberLiteralExpression literal_expression = + INTEGER.n {: return new NumberLiteralExpression(Integer.parseInt(n)); :} + | REAL.n {: return new NumberLiteralExpression(Double.parseDouble(n)); :} + ; + +Designator designator = + NAME.n {: return eph.createDesignator(n); :} ; Thing thing = THING COLON thing_body.tb SEMICOLON {: return tb; :} ; +// Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ; Thing thing_body = ID EQUALS TEXT.n thing_body.t {: return eph.setID(t, n); :} | LABEL EQUALS TEXT.n thing_body.t {: t.setLabel(n); return t; :} @@ -59,7 +109,6 @@ Thing thing_body = | CHANNELS EQUALS string_list.channels {: return eph.setChannels(new Thing(), channels); :} ; -//old: ITEM COLON item_body.ib SEMICOLON {: return ib; :} Item item = COLOR ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new ColorItem(), ib); :} | CONTACT ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new ContactItem(), ib); :} @@ -72,16 +121,21 @@ Item item = | ROLLER_SHUTTER ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new RollerShutterItem(), ib); :} | STRING ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new StringItem(), ib); :} | SWITCH ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new SwitchItem(), ib); :} + | ACTIVITY ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new ActivityItem(), ib); :} | ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new DefaultItem(), ib); :} ; -// ITEM_TYPE Item: id="" label="" state="" topic=""; +// ITEM_TYPE Item: id="" label="" state="" category="" topic="" controls=["ITEM_ID"] 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); :} + | CONTROLS EQUALS string_list.controlling item_body.i + {: return eph.setControlling(i, controlling); :} + | META_DATA EQUALS string_map.md item_body.i + {: return eph.setMetaData(i, md); :} | {: return eph.createItem(); :} ; @@ -89,7 +143,8 @@ Group group = GROUP COLON group_body.gb SEMICOLON {: return gb; :} ; -// Group: id="" groups=[] items=[]; +// 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 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; :} @@ -188,32 +243,88 @@ InfluxRoot influx_root_body = | {: return InfluxRoot.createDefault(); :} ; +// Machine Learning +MachineLearningRoot machine_learning_root = + ML COLON machine_learning_root_body.b SEMICOLON {: return b; :} + ; + +// ML: activities={index:"name"} ; +MachineLearningRoot machine_learning_root_body = + ACTIVITIES EQUALS integer_map.map machine_learning_root_body.b {: return eph.setActivities(b, map); :} + | {: return MachineLearningRoot.createDefault(); :} + ; + +// 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 {: return eph.createRule(c, a); :} + ; + +Condition condition = + logical_expression.be {: return new ExpressionCondition(be); :} + ; + +// TODO implement action cases +Action action = {: return new NoopAction(); :} + ; + +// Util: StringList, StringKeyMap, IntegerKeyMap StringList string_list = - LB_SQUARE string_list_body.slb RB_SQUARE {: return slb; :} + LB_SQUARE string_list_body.slb RB_SQUARE {: return slb; :} + | LB_SQUARE RB_SQUARE {: return new StringList(); :} ; StringList string_list_body = - TEXT.n COMMA string_list_body.slb {: slb.add(n); return slb; :} + TEXT.n COMMA string_list_body.slb {: slb.add(n); return slb; :} | TEXT.n {: StringList result = new StringList(); result.add(n); return result; :} - | {: return new StringList(); :} ; StringList round_string_list = - LB_ROUND round_string_list_body.slb RB_ROUND {: return slb; :} + LB_ROUND round_string_list_body.slb RB_ROUND {: return slb; :} + | LB_ROUND RB_ROUND {: return new StringList(); :} ; StringList round_string_list_body = - TEXT.n COMMA round_string_list_body.slb {: slb.add(n); return slb; :} + TEXT.n COMMA round_string_list_body.slb {: slb.add(n); return slb; :} | TEXT.n {: StringList result = new StringList(); result.add(n); return result; :} - | {: return new StringList(); :} + ; + +StringKeyMap string_map = + LB_CURLY string_map_body.smb RB_CURLY {: return smb; :} + | LB_CURLY RB_CURLY {: return new StringKeyMap(); :} + ; + +StringKeyMap string_map_body = + TEXT.key COLON TEXT.value COMMA string_map_body.smb {: smb.put(key, value); return smb; :} + | TEXT.key COLON TEXT.value + {: + StringKeyMap result = new StringKeyMap(); + result.put(key, value); + return result; + :} + ; + +IntegerKeyMap integer_map = + LB_CURLY integer_map_body.imb RB_CURLY {: return imb; :} + | LB_CURLY RB_CURLY {: return new IntegerKeyMap(); :} + ; + +IntegerKeyMap integer_map_body = + INTEGER.key COLON TEXT.value COMMA integer_map_body.imb {: imb.put(Integer.parseInt(key), value); return imb; :} + | INTEGER.key COLON TEXT.value + {: + IntegerKeyMap result = new IntegerKeyMap(); + result.put(Integer.parseInt(key), value); + return result; + :} ; diff --git a/eraser-base/src/main/jastadd/main.relast b/eraser-base/src/main/jastadd/main.relast index 47bf7304f545b320da804f17135ea21509db775f..066ffbbc6f4eeb18930c645d246cb57112a2c8a9 100644 --- a/eraser-base/src/main/jastadd/main.relast +++ b/eraser-base/src/main/jastadd/main.relast @@ -1,114 +1,12 @@ // ---------------- Main ------------------------------ -Root ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* User* MqttRoot InfluxRoot - MachineLearningRoot Rule* ItemObserver* ; - -// ---------------- openHAB ------------------------------ -abstract ModelElement ::= <ID:String> ; -abstract LabelledModelElement : ModelElement ::= <Label:String> ; -abstract DescribableModelElement : LabelledModelElement ::= <Description:String> ; - -ThingType : DescribableModelElement ::= Parameter* ; - -Thing : LabelledModelElement ::= Channel* ; - -ChannelType : DescribableModelElement ::= <ItemType:ItemType> <ReadOnly:boolean> ; - -abstract ChannelCategory ; -DefaultChannelCategory : ChannelCategory ::= <Value:DefaultChannelCategoryValue> ; -SimpleChannelCategory : ChannelCategory ::= <Value:String> ; - -Channel : ModelElement ::= <Type:ChannelType> Link* ; - -Link ::= <Item:Item> ; - -Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ; -ParameterDefaultValue ::= <Value:String> ; - -abstract Item : LabelledModelElement ::= <DefaultShouldSendState:boolean> <_fetched_data:boolean> ; -abstract ItemWithBooleanState : Item ::= <_state:boolean> ; -abstract ItemWithStringState : Item ::= <_state:String> ; -abstract ItemWithDoubleState : Item ::= <_state:double> ; -ColorItem : Item ::= <_state:TupleHSB> ; -DateTimeItem : Item ::= <_state:Date> ; -ContactItem : ItemWithBooleanState ; -DimmerItem : ItemWithDoubleState ; -ImageItem : ItemWithStringState ; -LocationItem : ItemWithStringState ; -NumberItem : ItemWithDoubleState ; -PlayerItem : ItemWithStringState ; -RollerShutterItem : ItemWithBooleanState ; -StringItem : ItemWithStringState ; -SwitchItem : ItemWithBooleanState ; -DefaultItem : ItemWithStringState ; - -ItemCategory ::= <Name:String> ; - -Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ; -abstract GroupAggregationFunction ; -SimpleGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:SimpleGroupAggregationFunctionName> ; -ParameterizedGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:ParameterizedGroupAggregationFunctionName> - <Param1:String> <Param2:String> ; +Root ::= OpenHAB2Model User* MqttRoot InfluxRoot MachineLearningRoot Rule* Location* ; // ---------------- Users ------------------------------ User : LabelledModelElement ; +rel Root.CurrentUser? -> User ; // ---------------- Util ------------------------------ ExternalHost ::= <HostName:String> <Port:int> ; -// ---------------- MQTT ------------------------------ -MqttRoot ::= Topic:MqttTopic* <IncomingPrefix:String> <OutgoingPrefix:String> [Host:ExternalHost] ; -MqttTopic ::= <Part:String> SubTopic:MqttTopic* ; - // ---------------- InfluxDB ------------------------------ InfluxRoot ::= <User:String> <Password:String> <DbName:String> [Host:ExternalHost] ; - -// ---------------- Machine Learning Model ------------------------------ -MachineLearningRoot ::= [ActivityRecognition:MachineLearningModel] [PreferenceLearning:MachineLearningModel] Activity* ChangeEvent* ; -Activity ::= <Identifier:int> <Label:String> ; -abstract ChangeEvent ::= <Identifier:int> <Timestamp:long> ChangedItem* ; -ChangedItem ::= <NewStateAsString:String> ; -RecognitionEvent : ChangeEvent ; -ManualChangeEvent : ChangeEvent ; -abstract MachineLearningModel ; -ItemPreference ::= <PreferredHSB:TupleHSB> ; - -// ---------------- Decision Tree ------------------------------ -DecisionTreeRoot : MachineLearningModel ::= RootRule:DecisionTreeRule ; -abstract DecisionTreeElement ::= Preference:ItemPreference*; -abstract DecisionTreeRule : DecisionTreeElement ::= Left:DecisionTreeElement Right:DecisionTreeElement <Label:String> ; -ItemStateCheckRule : DecisionTreeRule ::= ItemStateCheck ; -abstract ItemStateCheck ::= <Comparator:ComparatorType> ; -ItemStateNumberCheck : ItemStateCheck ::= <Value:double> ; -ItemStateStringCheck : ItemStateCheck ::= <Value:String> ; -DecisionTreeLeaf : DecisionTreeElement ::= <ActivityIdentifier:int> <Label:String> ; - -// ---------------- Neural Network ------------------------------ -NeuralNetworkRoot : MachineLearningModel ::= InputNeuron* HiddenNeuron* OutputLayer ; -OutputLayer ::= OutputNeuron* <Combinator:DoubleArrayDoubleFunction> ; -abstract Neuron ::= Output:NeuronConnection* ; -NeuronConnection ::= <Weight:double> ; -InputNeuron : Neuron ; -HiddenNeuron : Neuron ::= <ActivationFormula:DoubleArrayDoubleFunction> ; -OutputNeuron : HiddenNeuron ::= <Label:String> ; - -// ---------------- ECA-Rules ------------------------------ -Rule ::= Event* <Condition:ConditionFunction> <Action:ActionEditConsumer> ; -Event ::= <Label:String> ; -ItemObserver ::= ; - -// ---------------- Relations ------------------------------ -rel ThingType.ChannelType* -> ChannelType ; -rel Thing.Type -> ThingType ; -rel ChannelType.ChannelCategory -> ChannelCategory ; -rel Item.Topic <-> MqttTopic.Item ; -rel Item.Category? -> ItemCategory ; -rel ItemStateCheck.Item -> Item ; -rel NeuronConnection.Neuron <-> Neuron.Input* ; -rel InputNeuron.Item -> Item ; -rel Item.Observer? <-> ItemObserver.ObservedItem ; -rel ItemObserver.TriggeredEvent* <-> Event.Observer ; -rel ItemPreference.Item -> Item ; -rel Item.Controlling* <-> Item.ControlledBy ; -rel Root.CurrentUser? -> User ; -rel ChangedItem.Item -> Item ; -rel RecognitionEvent.Activity -> Activity ; diff --git a/eraser-base/src/main/jastadd/mqtt.jrag b/eraser-base/src/main/jastadd/mqtt.jrag index db748d03d62c87fce2ebce9b0d66cf37ee225344..b3f9c2a24a0a62ccc1f360fdadaad4cd7bafe3b5 100644 --- a/eraser-base/src/main/jastadd/mqtt.jrag +++ b/eraser-base/src/main/jastadd/mqtt.jrag @@ -3,64 +3,41 @@ aspect MQTT { // --- default values --- private static final int MqttRoot.DEFAULT_PORT = 1883; - java.util.Set<String> MqttRoot.ignoredTopics = new java.util.HashSet<>(); - //--- resolveTopic --- syn java.util.Optional<MqttTopic> MqttRoot.resolveTopic(String topic) { ensureCorrectPrefixes(); if (!topic.startsWith(getIncomingPrefix())) { - logger.debug("Topic '{}' does not start with incoming prefix '{}'", topic, getIncomingPrefix()); + logger.warn("Topic '{}' does not start with incoming prefix '{}'", topic, getIncomingPrefix()); return java.util.Optional.empty(); } - topic = topic.substring(getIncomingPrefix().length()); - String[] tokens = topic.split("/"); - int tokenIndex = 0; - java.util.Optional<MqttTopic> result = check(tokens, 0, getTopics()); - if (!result.isPresent() && !ignoredTopics.contains(topic)) { - logger.error("Could not resolve {}, ignoring it.", topic); - ignoredTopics.add(topic); - } - return result; + String suffix = topic.substring(getIncomingPrefix().length()); + return resolveTopicSuffix(suffix); } - java.util.Optional<MqttTopic> MqttRoot.check(String[] tokens, int tokenIndex, JastAddList<MqttTopic> topics) { - for (MqttTopic current : topics) { - if (tokens[tokenIndex].equals(current.getPart())) { - // topic part matches, move on or return if tokens are empty - ++tokenIndex; - if (tokens.length == tokenIndex) { - return java.util.Optional.of(current); - } else { - return check(tokens, tokenIndex, current.getSubTopics()); - } + //--- resolveTopicSuffix --- + syn java.util.Optional<MqttTopic> MqttRoot.resolveTopicSuffix(String suffix) { + for (MqttTopic current : getTopics()) { + if (current.getTopicString().equals(suffix)) { + return Optional.of(current); } } - return java.util.Optional.empty(); + return Optional.empty(); } public void MqttRoot.ensureCorrectPrefixes() { - if (!getIncomingPrefix().endsWith("/")) { + if (!getIncomingPrefix().isEmpty() && !getIncomingPrefix().endsWith("/")) { setIncomingPrefix(getIncomingPrefix() + "/"); } - if (!getOutgoingPrefix().endsWith("/")) { + if (!getOutgoingPrefix().isEmpty() && !getOutgoingPrefix().endsWith("/")) { setOutgoingPrefix(getOutgoingPrefix() + "/"); } } //--- getIncomingTopic --- - syn String MqttTopic.getIncomingTopic() = getMqttRoot().getIncomingPrefix() + allParts(); + syn String MqttTopic.getIncomingTopic() = getMqttRoot().getIncomingPrefix() + getTopicString(); //--- getOutgoingTopic --- - syn String MqttTopic.getOutgoingTopic() = getMqttRoot().getOutgoingPrefix() + allParts(); - - //--- allParts --- - inh String MqttTopic.allParts(); - eq MqttTopic.getSubTopic(int i).allParts() { - return allParts() + "/" + getSubTopic(i).getPart(); - } - eq MqttRoot.getTopic(int i).allParts() { - return getTopic(i).getPart(); - } + syn String MqttTopic.getOutgoingTopic() = getMqttRoot().getOutgoingPrefix() + getTopicString(); //--- getMqttSender (should be cached) --- cache MqttRoot.getMqttSender(); @@ -78,12 +55,12 @@ aspect MQTT { inh MqttRoot MqttTopic.getMqttRoot(); eq MqttRoot.getTopic().getMqttRoot() = this; - eq MqttTopic.getSubTopic().getMqttRoot() = getMqttRoot(); /** * Sends the current state via MQTT. */ - public void Item.sendState() throws Exception { + refine ItemHandling protected void Item.sendState() throws Exception { + refined(); if (getTopic() != null) { getTopic().send(getStateAsString()); } @@ -97,4 +74,14 @@ aspect MQTT { getMqttRoot().getMqttSender().publish(getOutgoingTopic(), message); } + refine OpenHAB2 public void OpenHAB2Model.addNewItem(Item item) { + refined(item); + // update mqtt-topic to new mqtt-root + JavaUtils.ifPresentOrElse( + getRoot().getMqttRoot().resolveTopicSuffix(item.getTopic().getTopicString()), + topic -> item.setTopic(topic), + () -> de.tudresden.inf.st.eraser.util.ParserUtils.createMqttTopic(item, item.getTopic().getTopicString(), getRoot()) + ); + } + } diff --git a/eraser-base/src/main/jastadd/mqtt.relast b/eraser-base/src/main/jastadd/mqtt.relast new file mode 100644 index 0000000000000000000000000000000000000000..cf477ff72e303415e47f42c939afe26cdfc4ed54 --- /dev/null +++ b/eraser-base/src/main/jastadd/mqtt.relast @@ -0,0 +1,4 @@ +// ---------------- MQTT ------------------------------ +MqttRoot ::= Topic:MqttTopic* <IncomingPrefix:String> <OutgoingPrefix:String> [Host:ExternalHost] ; +MqttTopic ::= <TopicString:String> ; +rel Item.Topic? <-> MqttTopic.Item* ; diff --git a/eraser-base/src/main/jastadd/openhab.jrag b/eraser-base/src/main/jastadd/openhab.jrag new file mode 100644 index 0000000000000000000000000000000000000000..56aa89f36ac62aa817352f45a639ae7c4aa62206 --- /dev/null +++ b/eraser-base/src/main/jastadd/openhab.jrag @@ -0,0 +1,13 @@ +aspect OpenHAB2 { + syn ActivityItem OpenHAB2Model.getActivityItem() { + return new ActivityItem(); + } + + public void OpenHAB2Model.addNewItem(Item item) { + JavaUtils.ifPresentOrElse( + resolveGroup(de.tudresden.inf.st.eraser.util.ParserUtils.UNKNOWN_GROUP_NAME), + group -> group.addItem(item), + () -> de.tudresden.inf.st.eraser.util.ParserUtils.createUnknownGroup(this, Collections.singletonList(item))); + } + +} diff --git a/eraser-base/src/main/jastadd/openhab.relast b/eraser-base/src/main/jastadd/openhab.relast new file mode 100644 index 0000000000000000000000000000000000000000..575f875734994ee52ba5259e5cfe570aa488c139 --- /dev/null +++ b/eraser-base/src/main/jastadd/openhab.relast @@ -0,0 +1,57 @@ +// ---------------- openHAB ------------------------------ +OpenHAB2Model ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* /ActivityItem:Item/ ; + +abstract ModelElement ::= <ID:String> ; +abstract LabelledModelElement : ModelElement ::= <Label:String> ; +abstract DescribableModelElement : LabelledModelElement ::= <Description:String> ; + +ThingType : DescribableModelElement ::= Parameter* ; +rel ThingType.ChannelType* -> ChannelType ; + +Thing : LabelledModelElement ::= Channel* ; +rel Thing.Type -> ThingType ; + +ChannelType : DescribableModelElement ::= <ItemType:ItemType> <ReadOnly:boolean> ; +rel ChannelType.ChannelCategory -> ChannelCategory ; + +abstract ChannelCategory ; +DefaultChannelCategory : ChannelCategory ::= <Value:DefaultChannelCategoryValue> ; +SimpleChannelCategory : ChannelCategory ::= <Value:String> ; + +Channel : ModelElement ::= ; +rel Channel.Type -> ChannelType ; +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] ; +rel Item.Category? -> ItemCategory ; +rel Item.Controlling* <-> Item.ControlledBy* ; + +abstract ItemWithBooleanState : Item ::= <_state:boolean> ; +abstract ItemWithStringState : Item ::= <_state:String> ; +abstract ItemWithDoubleState : Item ::= <_state:double> ; +ColorItem : Item ::= <_state:TupleHSB> ; +DateTimeItem : Item ::= <_state:Instant> ; +ContactItem : ItemWithBooleanState ; +DimmerItem : ItemWithDoubleState ; +ImageItem : ItemWithStringState ; +LocationItem : ItemWithStringState ; +NumberItem : ItemWithDoubleState ; +PlayerItem : ItemWithStringState ; +RollerShutterItem : ItemWithDoubleState ; +StringItem : ItemWithStringState ; +SwitchItem : ItemWithBooleanState ; +DefaultItem : ItemWithStringState ; +ActivityItem : ItemWithDoubleState ; + +ItemMetaData ::= <Key:String> <Value:String> ; + +ItemCategory ::= <Name:String> ; + +Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ; +abstract GroupAggregationFunction ; +SimpleGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:SimpleGroupAggregationFunctionName> ; +ParameterizedGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:ParameterizedGroupAggregationFunctionName> + <Param1:String> <Param2:String> ; diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/Main.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/Main.java index 9b98f688fce2bba0dadffeb6584f03ff1f28628d..9b9aaba5b25e860177545415c5ca1c088a3e7165 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/Main.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/Main.java @@ -6,8 +6,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.mqtt.MQTTUpdater; import de.tudresden.inf.st.eraser.util.ParserUtils; -import org.apache.commons.math3.linear.MatrixUtils; -import org.apache.commons.math3.linear.RealMatrix; import org.apache.logging.log4j.LogManager; import java.io.*; @@ -16,7 +14,7 @@ import java.io.*; * Main entry point for testing eraser. * @author rschoene - Initial contribution */ -@SuppressWarnings({"unused", "WeakerAccess", "RedundantThrows"}) +@SuppressWarnings({"unused", "RedundantThrows"}) public class Main { public static void main(String[] args) throws IOException, Parser.Exception { @@ -25,35 +23,6 @@ public class Main { // Root model = importFromOpenHab(); // testPrinterWith(model); // testUpdaterWith(model); - testXY_to_RGB(); - } - - private static void testXY_to_RGB() { - /* - XYZ to RGB [M]-1 - 2.0413690 -0.5649464 -0.3446944 - -0.9692660 1.8760108 0.0415560 - 0.0134474 -0.1183897 1.0154096 - */ - double[][] matrixData = { { 2.0413690, -0.5649464, -0.3446944}, - {-0.9692660, 1.8760108, 0.0415560}, - { 0.0134474, -0.1183897, 1.0154096}}; - RealMatrix mInverted = MatrixUtils.createRealMatrix(matrixData); - BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); - - while (true) { - try { - double x = readFromSystemIn(in, "x:"); - double y = readFromSystemIn(in, "y:"); - double z = 1; - RealMatrix xyz = MatrixUtils.createColumnRealMatrix(new double[] {x,y,z}); - RealMatrix result = mInverted.multiply(xyz); - System.out.println(result); - } catch (IOException | NumberFormatException e) { - e.printStackTrace(); - break; - } - } } private static double readFromSystemIn(BufferedReader in, String prompt) throws IOException { @@ -70,7 +39,7 @@ public class Main { private static void testPrinterWith(Root model) { model.flushTreeCache(); - System.out.println("Got model: " + model.description()); + System.out.println("Got model: " + model.getOpenHAB2Model().description()); System.out.println("PrettyPrinted:"); System.out.println(model.prettyPrint()); } @@ -79,7 +48,7 @@ public class Main { Root model; // model = importFromOpenHab(); model = importFromFile(); - System.out.println("Got model: " + model.description()); + System.out.println("Got model: " + model.getOpenHAB2Model().description()); // JsonSerializer.write(model, "openhab2-data.json"); testUpdaterWith(model); } @@ -106,7 +75,7 @@ public class Main { public static Root importFromOpenHab() { OpenHab2Importer importer = new OpenHab2Importer(); - return importer.importFrom("192.168.1.250", 8080); + return importer.importFrom("192.168.1.250", 8080).getRoot(); } public static Root importFromFile() { diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/deserializer/ASTNodeDeserializer.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/deserializer/ASTNodeDeserializer.java index 95f1d470c014ce4a123b5e01e33e7b049ba8ceff..d349331de9e8e43b7513001c659f94e61cc94ae8 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/deserializer/ASTNodeDeserializer.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/deserializer/ASTNodeDeserializer.java @@ -48,12 +48,17 @@ public class ASTNodeDeserializer extends StdDeserializer<ASTNode> { r.put(c.getName(), ((node, model) -> f.apply(model, termValue(node, terminalName)))); } + private void addResolverForOpenHAB2Model(Map<String, ResolveAstNodeForOpenHAB2Model> r, Class<?> c, BiFunction<OpenHAB2Model, String, Optional<? extends ASTNode>> f, String terminalName) { + r.put(c.getName(), ((node, model) -> f.apply(model, termValue(node, terminalName)))); + } + private Map<String, ResolveAstNode> resolvers = new HashMap<>(); + private Map<String, ResolveAstNodeForOpenHAB2Model> resolversForOpenHAB2Model = new HashMap<>(); private void initResolvers() { - addResolver(resolvers, ThingType.class, Root::resolveThingType, "ID"); - addResolver(resolvers, ChannelType.class, Root::resolveChannelType, "ID"); - addResolver(resolvers, Item.class, Root::resolveItem, "ID"); + addResolverForOpenHAB2Model(resolversForOpenHAB2Model, ThingType.class, OpenHAB2Model::resolveThingType, "ID"); + addResolverForOpenHAB2Model(resolversForOpenHAB2Model, ChannelType.class, OpenHAB2Model::resolveChannelType, "ID"); + addResolverForOpenHAB2Model(resolversForOpenHAB2Model, Item.class, OpenHAB2Model::resolveItem, "ID"); addResolver(resolvers, MqttTopic.class, Root::resolveMqttTopic, "IncomingTopic"); } @@ -363,11 +368,15 @@ public class ASTNodeDeserializer extends StdDeserializer<ASTNode> { } } - interface ResolveAstNode { Optional<? extends ASTNode> resolve(JsonNode node, Root model) throws IOException; } + +interface ResolveAstNodeForOpenHAB2Model { + Optional<? extends ASTNode> resolve(JsonNode node, OpenHAB2Model model) throws IOException; +} + class ResolveLater { JsonNode node; diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/Action2EditConsumer.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/Action2EditConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..e37a3238af4356278c8e1d4bb8808ab9b1948fd1 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/Action2EditConsumer.java @@ -0,0 +1,11 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.function.Consumer; + +/** + * Consumer used to make edits in the action part of ECA rules. + * + * @author rschoene - Initial contribution + */ +public interface Action2EditConsumer extends Consumer<Item> { +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/DoubleDoubleFunction.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/DoubleDoubleFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..6a8625d2ae9be07861d1e80312b7bf2a4fb39494 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/DoubleDoubleFunction.java @@ -0,0 +1,11 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.function.Function; + +/** + * Function, that takes a double as input and outputs a double. + * + * @author rschoene - Initial contribution + */ +public interface DoubleDoubleFunction extends Function<Double, Double> { +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InfluxAdapter.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InfluxAdapter.java index b5e0ea7fa11960184c09f3a328e02ecee3d0608f..15704f45da1f36963a0d4c884bb1e70b9721b126 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InfluxAdapter.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InfluxAdapter.java @@ -46,7 +46,7 @@ public abstract class InfluxAdapter implements AutoCloseable { /** * Forces this adapter to compute {@link #asyncQuery(String, String, Class, QueryResultCallback)} synchronously. */ - public void diableAsyncQuery() { + public void disableAsyncQuery() { this.reallyAsync = false; } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..75d2a1d8cfc28c0293f1d55cc4e6714a6e37818a --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java @@ -0,0 +1,59 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.time.Instant; +import java.util.List; + +/** + * Adapter for internally held machine learning models. + * + * @author rschoene - Initial contribution + */ +public class InternalMachineLearningHandler implements MachineLearningEncoder, MachineLearningDecoder { + + private static final Logger logger = LogManager.getLogger(InternalMachineLearningHandler.class); + private InternalMachineLearningModel model; + + public InternalMachineLearningHandler setModel(InternalMachineLearningModel model) { + this.model = model; + return this; + } + + @Override + public void setKnowledgeBaseRoot(Root root) { + // ignored + } + + @Override + public void newData(List<Item> changedItems) { + logger.debug("Ignored new data of {}", changedItems); + } + + @Override + public List<Item> getTargets() { + return model.getTargetItems(); + } + + @Override + public List<Item> getRelevantItems() { + return model.getRelevantItems(); + } + + @Override + public void triggerTraining() { + logger.debug("Ignored training trigger."); + } + + @Override + public MachineLearningResult classify() { + List<ItemPreference> preferences = model.classify().computePreferences(); + return new InternalMachineLearningResult(preferences); + } + + @Override + public Instant lastModelUpdate() { + return null; + } +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningResult.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningResult.java new file mode 100644 index 0000000000000000000000000000000000000000..087020b383fee89ea839b2056e22072af799a9f3 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningResult.java @@ -0,0 +1,21 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.List; + +/** + * Result of a classification returned by an internally held machine learning model. + * + * @author rschoene - Initial contribution + */ +public class InternalMachineLearningResult implements MachineLearningResult { + private final List<ItemPreference> preferences; + + InternalMachineLearningResult(List<ItemPreference> preferences) { + this.preferences = preferences; + } + + @Override + public List<ItemPreference> getPreferences() { + return this.preferences; + } +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/ItemsToStringFunction.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/ItemsToStringFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..ec5e4772263fe2588baab69759dddf375676188f --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/ItemsToStringFunction.java @@ -0,0 +1,11 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.function.Function; + +/** + * Merge states from given items into one (generic) state represented as a string. + * + * @author rschoene - Initial contribution + */ +public interface ItemsToStringFunction extends Function<Iterable<Item>, String> { +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..045c7695ba325429a3ba82a4e1319e0926aa043b --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java @@ -0,0 +1,28 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.time.Instant; + +/** + * This interface represents the connection from a machine learning model back to the knowledge base. + * It decodes the output of the machine learning model and outputs the result of the classification. + * + * @author rschoene - Initial contribution + */ +@SuppressWarnings("unused") +public interface MachineLearningDecoder extends MachineLearningSetRoot { + + /** + * Execute the machine learning model and returns the classification result. + * @return the result of the classification + */ + MachineLearningResult classify(); + + // less important + + /** + * Returns the time when the model was last updated, i.e., when the last training was completed. + * @return the time when the model was last updated, or <code>null</code> if the model was not trained yet + */ + Instant lastModelUpdate(); + +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..23ca8959f468b8210c287b163a217a0224f7d283 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java @@ -0,0 +1,44 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.List; + +/** + * This interface represents the connection from knowledge base to one machine learning model. + * It takes information from the knowledge base, and encodes them to a representation that is readable both for + * the used technique and the purpose of the machine learning model. + * + * @author rschoene - Initial contribution + */ +@SuppressWarnings("unused") +public interface MachineLearningEncoder extends MachineLearningSetRoot { + + /** + * Update when new data is available. + * @param changedItems A list of items whose state has changed + */ + void newData(List<Item> changedItems); + + // to be discussed, in which form this is specified + + /** + * Get the items that this model is supposed to change. + * @return the list of targeted items + */ + List<Item> getTargets(); + + // to be discussed, in which form this is specified + + /** + * Get the items which are relevant for the decision making of this model. + * @return the list of items relevant for decision making + */ + List<Item> getRelevantItems(); + + // to be discussed, if this is necessary + + /** + * Explicit hint for this model to start/trigger training. The model might ignore this hint. + */ + void triggerTraining(); + +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java new file mode 100644 index 0000000000000000000000000000000000000000..eebd16d3679c1531cb02d1ba1abd0c1610303668 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java @@ -0,0 +1,25 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference; + +import java.util.List; + +/** + * Representation of a classification result using a MachineLearningModel. + * + * @author rschoene - Initial contribution + */ +@SuppressWarnings("unused") +public interface MachineLearningResult { + + // Object rawClass(); + + // double rawConfidence(); + + // can be used for both activity and preferences + /** + * Get the result as a list of item preferences, i.e., new states to be set for those items. + * @return the classification result as item preferences + */ + List<ItemPreference> getPreferences(); +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningSetRoot.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningSetRoot.java new file mode 100644 index 0000000000000000000000000000000000000000..13fec09c243d0d15c4f5e41a8c8851321c26d5d5 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningSetRoot.java @@ -0,0 +1,17 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +/** + * Common interface for both {@link MachineLearningDecoder} and {@link MachineLearningEncoder}. + * + * @author rschoene - Initial contribution + */ +public interface MachineLearningSetRoot { + + /** + * Informs this handler of the knowledge base. + * This method is called before any other of the interface methods. + * @param root The root node of the knowledge base + */ + void setKnowledgeBaseRoot(Root root); + +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/NewStateProvider.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/NewStateProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..eecbc504eda4ffd73a336032952e96ee2d56d34e --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/NewStateProvider.java @@ -0,0 +1,11 @@ +package de.tudresden.inf.st.eraser.jastadd.model; + +import java.util.function.Supplier; + +/** + * Provide a new state in form of a string which is set to the item. + * + * @author rschoene - Initial contribution + */ +public interface NewStateProvider extends Supplier<String> { +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/TupleHSB.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/TupleHSB.java index a3520402e367ba88ab6f5f218915f3b0c5447f84..8e9ee2ad31f8d16ff415bcb7fe82bd34eb87f02a 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/TupleHSB.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/TupleHSB.java @@ -8,9 +8,9 @@ import java.util.Objects; * @author rschoene - Initial contribution */ public class TupleHSB implements Cloneable { - public int hue; - public int saturation; - public int brightness; + 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; @@ -19,6 +19,30 @@ public class TupleHSB implements Cloneable { return result; } + 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); } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java index b7deb57f7fe10e46fe555c74e75eeea864c857bc..4e1742c7f48b31e040cc70866e8ecd5070622172 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; public class OpenHab2Importer { protected static final String thingTypesUrl = "http://%s/rest/thing-types"; + protected static final String thingTypeDetailUrl = "http://%s/rest/thing-types/%s"; protected static final String channelTypeUrl = "http://%s/rest/channel-types"; protected static final String thingsUrl = "http://%s/rest/things"; protected static final String itemsUrl = "http://%s/rest/items"; @@ -41,7 +42,7 @@ public class OpenHab2Importer { nonDefaultChannelCategories = new HashSet<>(); } - public Root importFrom(String host, int port) { + public OpenHAB2Model importFrom(String host, int port) { /* Plan: - requesting: thing-types, channel-types, things, items, links @@ -57,7 +58,8 @@ public class OpenHab2Importer { mapper.registerModule(module); try { - Root model = Root.createEmptyRoot(); + Root root = Root.createEmptyRoot(); + OpenHAB2Model model = root.getOpenHAB2Model(); ThingTypeData[] thingTypeList = mapper.readValue(makeURL(thingTypesUrl, hostAndPort), ThingTypeData[].class); logger.info("Read a total of {} thing type(s).", thingTypeList.length); update(model, thingTypeList); @@ -78,19 +80,14 @@ public class OpenHab2Importer { logger.info("Read a total of {} link(s).", linkList.length); update(model, linkList); - // create empty MQTT root - MqttRoot mqttRoot = new MqttRoot(); - mqttRoot.setHostByName(host); - model.setMqttRoot(mqttRoot); - - // create empty Influx root - InfluxRoot influxRoot = InfluxRoot.createDefault(); - influxRoot.setHostByName(host); - model.setInfluxRoot(influxRoot); - + // get parameters of thing types + for (ThingType thingType : model.getThingTypeList()) { + ThingTypeData data = mapper.readValue(makeURL(thingTypeDetailUrl, hostAndPort, thingType.getID()), ThingTypeData.class); + update(thingType, data); + } return model; } catch (IOException e) { - e.printStackTrace(); + logger.catching(e); return null; } } @@ -99,7 +96,11 @@ public class OpenHab2Importer { return URI.create(String.format(formatUrlString, hostAndPort)).toURL(); } - private void update(Root model, ThingTypeData[] thingTypeList) { + protected URL makeURL(String formatUrlString, String hostAndPort, String id) throws MalformedURLException { + return URI.create(String.format(formatUrlString, hostAndPort, id)).toURL(); + } + + private void update(OpenHAB2Model model, ThingTypeData[] thingTypeList) { for (ThingTypeData thingTypeData : thingTypeList) { ThingType thingType = new ThingType(); thingType.setID(thingTypeData.UID); @@ -109,7 +110,7 @@ public class OpenHab2Importer { } } - private void update(Root model, ChannelTypeData[] channelTypeList) { + private void update(OpenHAB2Model model, ChannelTypeData[] channelTypeList) { for (ChannelTypeData channelTypeData : channelTypeList) { ChannelType channelType = new ChannelType(); channelType.setID(channelTypeData.UID); @@ -162,7 +163,7 @@ public class OpenHab2Importer { } } - private void update(Root model, ThingData[] thingList) { + private void update(OpenHAB2Model model, ThingData[] thingList) { for (ThingData thingData : thingList) { Thing thing = new Thing(); thing.setID(thingData.UID); @@ -181,7 +182,7 @@ public class OpenHab2Importer { } } - private void update(Root model, AbstractItemData[] itemList) { + private void update(OpenHAB2Model model, AbstractItemData[] itemList) { List<Tuple<Group, GroupItemData>> groupsWithMembers = new ArrayList<>(); List<Tuple<Group, GroupItemData>> groupsInGroups = new ArrayList<>(); List<Tuple<Item, AbstractItemData>> itemsInGroups = new ArrayList<>(); @@ -234,11 +235,21 @@ public class OpenHab2Importer { })); } // if state is not set in openHAB, then use default state for the item + item.disableSendState(); if (itemData.state.equals(NULL_STATE)) { item.setStateToDefault(); } else { item.setStateFromString(itemData.state, false); } + item.enableSendState(); + if (itemData.metadata != null) { + for (Map.Entry<String, MetaDataData> entry : itemData.metadata.entrySet()) { + logger.debug("Add metadata for namespace {}", entry.getKey()); + for (Map.Entry<String, String> metaDataEntry : entry.getValue().config.entrySet()) { + item.addMetaData(new ItemMetaData(metaDataEntry.getKey(), metaDataEntry.getValue())); + } + } + } if (itemData.groupNames.size() > 0) { itemsInGroups.add(Tuple.of(item, itemData)); } else { @@ -288,15 +299,38 @@ public class OpenHab2Importer { } } - private void update(Root model, LinkData[] linkList) { + private void update(OpenHAB2Model model, LinkData[] linkList) { for (LinkData linkData : linkList) { - Link link = new Link(); ifPresent(model.resolveChannel(linkData.channelUID), "Channel", linkData, - channel -> channel.addLink(link)); - ifPresent(model.resolveItem(linkData.itemName), "Item", linkData, link::setItem); + channel -> ifPresent(model.resolveItem(linkData.itemName), "Item", linkData, channel::addLinkedItem)); } } + private void update(ThingType thingType, ThingTypeData data) { + // just set parameters + if (data == null || data.configParameters == null) { + logger.warn("No parameters given for thing type {}", thingType.getID()); + return; + } + for (ParameterData parameterData : data.configParameters) { + Parameter parameter = new Parameter(); + parameter.setID(parameterData.name); + parameter.setLabel(parameterData.label); + parameter.setDescription(parameterData.description); + parameter.setContext(parameterData.context); + if (parameterData.defaultValue != null) { + parameter.setDefaultValue(new ParameterDefaultValue(parameterData.defaultValue)); + } + parameter.setRequired(parameterData.required); + parameter.setType(ParameterValueType.valueOf(titleCase(parameterData.type))); + thingType.addParameter(parameter); + } + } + + private String titleCase(String type) { + return Character.toTitleCase(type.charAt(0)) + type.substring(1).toLowerCase(); + } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private <T> void ifPresent(Optional<T> opt, String type, Object data, Consumer<? super T> consumer) { @@ -307,7 +341,7 @@ public class OpenHab2Importer { } } - public Root importFrom(URL baseUrl) { + public OpenHAB2Model importFrom(URL baseUrl) { return importFrom(baseUrl.getHost(), baseUrl.getPort() == -1 ? baseUrl.getDefaultPort() : baseUrl.getPort()); } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/AbstractItemData.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/AbstractItemData.java index 519a0c776fde351d371f4deafe3abac76d0a7f6f..4ea65352764f22f9053544d7b91b3e0a1773877a 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/AbstractItemData.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/AbstractItemData.java @@ -3,6 +3,7 @@ package de.tudresden.inf.st.eraser.openhab2.data; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.List; +import java.util.Map; /** * Abstract JSON return type for either an item or a group in openHAB 2. @@ -23,4 +24,5 @@ public class AbstractItemData { public String transformedState; public StateDescriptionData stateDescription; public Boolean editable; + public Map<String, MetaDataData> metadata; } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/MetaDataData.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/MetaDataData.java new file mode 100644 index 0000000000000000000000000000000000000000..d15cbef4177f279de3cdd318b3ca2853cab2be94 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/MetaDataData.java @@ -0,0 +1,17 @@ +package de.tudresden.inf.st.eraser.openhab2.data; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Map; + +/** + * Item Meta data. + * + * @author rschoene - Initial contribution + */ +@SuppressWarnings("unused") +@JsonInclude(JsonInclude.Include.NON_DEFAULT) +public class MetaDataData { + public String value; + public Map<String, String> config; +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ParameterData.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ParameterData.java index 12571c8caa6f1c5348ca65a94ca0e7e41e57ad00..ce6b5c350c438e4755ff0e6eaa960d192492ba01 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ParameterData.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ParameterData.java @@ -9,7 +9,7 @@ import java.util.List; * * @author rschoene - Initial contribution */ -@SuppressWarnings("unused") +//@SuppressWarnings("unused") @JsonInclude(JsonInclude.Include.NON_DEFAULT) public class ParameterData { public String context; @@ -19,24 +19,24 @@ public class ParameterData { public String name; public Boolean required; public String type; - public Integer min; +// public Integer min; public Integer max; - public Integer stepsize; +// public Integer stepsize; public String pattern; public Boolean readOnly; - public Boolean multiple; - public Integer multipleLimit; - public String groupName; - public Boolean advanced; - public Boolean verify; - public Boolean limitToOptions; +// public Boolean multiple; +// public Integer multipleLimit; +// public String groupName; +// public Boolean advanced; +// public Boolean verify; +// public Boolean limitToOptions; public String unit; - public String unitLabel; - public List<OptionData> options; - public List<FilterCriteriaData> filterCriteria; +// public String unitLabel; +// public List<OptionData> options; +// public List<FilterCriteriaData> filterCriteria; - public static class FilterCriteriaData { - public String name; - public String value; - } +// public static class FilterCriteriaData { +// public String name; +// public String value; +// } } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ThingTypeData.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ThingTypeData.java index 8b73dd318654a000658fa2160cb4f1f63972f11e..a4fd4cbca59cba456cd273b55aa2ed435b059e85 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ThingTypeData.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/data/ThingTypeData.java @@ -19,4 +19,5 @@ public class ThingTypeData { public boolean listed; public List<String> supportedBridgeTypeUIDs; public boolean bridge; + public List<ParameterData> configParameters; } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/mqtt/MQTTUpdater.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/mqtt/MQTTUpdater.java index 2bf5880087ccc171ea4e2799c0db4fa1bda11e9d..647114843839aef335b5337cb62e744870d3a0e3 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/mqtt/MQTTUpdater.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/mqtt/MQTTUpdater.java @@ -1,23 +1,15 @@ package de.tudresden.inf.st.eraser.openhab2.mqtt; +import de.tudresden.inf.st.eraser.jastadd.model.ExternalHost; import de.tudresden.inf.st.eraser.jastadd.model.Item; import de.tudresden.inf.st.eraser.jastadd.model.Root; import de.tudresden.inf.st.eraser.util.MqttReceiver; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.fusesource.hawtbuf.Buffer; -import org.fusesource.hawtbuf.UTF8Buffer; -import org.fusesource.mqtt.client.*; +import org.fusesource.mqtt.client.QoS; import java.io.IOException; -import java.net.URI; -import java.util.Arrays; -import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Update an imported model by subscribing to MQTT topics. @@ -36,28 +28,33 @@ public class MQTTUpdater implements AutoCloseable { this.delegatee = new MqttReceiver(); } - public MQTTUpdater(Root model) throws IllegalArgumentException { + public MQTTUpdater(Root root) throws IllegalArgumentException { this(); - this.setModel(model); + this.setRoot(root); } /** - * Sets the model to update - * @param model the model to update + * Sets the model root to update + * @param root the model root to update */ - public void setModel(Root model) { - delegatee.setHost(model.getMqttRoot().getHost().getHostName()); + public void setRoot(Root root) { + ExternalHost host = root.getMqttRoot().getHost(); + delegatee.setHost(host.getHostName(), host.getPort()); delegatee.setOnMessage((topicString, message) -> - model.getMqttRoot().resolveTopic(topicString).ifPresent(topic -> - itemUpdate(topic.getItem(), message))); - delegatee.setTopicsForSubscription(model.getMqttRoot().getIncomingPrefix() + "#"); + root.getMqttRoot().resolveTopic(topicString).ifPresent(topic -> + topic.getItems().forEach( + item -> itemUpdate(item, message)))); + delegatee.setTopicsForSubscription(root.getMqttRoot().getIncomingPrefix() + "#"); delegatee.setQoSForSubscription(QoS.AT_LEAST_ONCE); } private void itemUpdate(Item item, String state) { - this.logger.debug("Update state of {} [{}] from '{}' to '{}'.", - item.getLabel(), item.getID(), item.getStateAsString(), state); - item.setStateFromString(state, false); + String oldState = item.getStateAsString(); + if (oldState == null || !oldState.equals(state)) { + this.logger.debug("Update state of {} [{}] from '{}' to '{}'.", + item.getLabel(), item.getID(), oldState, state); + item.setStateFromString(state, false); + } } /** diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java index 5b1ce8810e96c928bf5f7b722698bd76a66b42b2..5d1b1af27c4bf36e15768bd12facc02ca3a1e773 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java @@ -1,6 +1,7 @@ package de.tudresden.inf.st.eraser.parser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.JavaUtils; import de.tudresden.inf.st.eraser.util.ParserUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,9 +29,11 @@ public class EraserParserHelper { 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<>(); @@ -44,6 +47,7 @@ public class EraserParserHelper { private Root root; private static boolean checkUnusedElements = true; + private static Root initialRoot = null; private class ItemPrototype extends DefaultItem { @@ -57,25 +61,41 @@ public class EraserParserHelper { EraserParserHelper.checkUnusedElements = checkUnusedElements; } + public static void setInitialRoot(Root root) { + EraserParserHelper.initialRoot = root; + } + /** * Post processing step after parsing a model, to resolve all references within the model. - * @throws java.util.NoSuchElementException if a reference can not be resolved */ public void resolveReferences() { + if (this.root == null) { + // when parsing expressions + this.root = EraserParserHelper.initialRoot != null ? EraserParserHelper.initialRoot : createRoot(); + } if (checkUnusedElements) { fillUnused(); } resolve(thingTypeMap, missingThingTypeMap, Thing::setType); resolve(channelTypeMap, missingChannelTypeMap, Channel::setType); + if (itemMap == null || itemMap.isEmpty()) { + missingItemForDesignator.forEach((designator, itemName) -> + JavaUtils.ifPresentOrElse(root.getOpenHAB2Model().resolveItem(itemName), + designator::setItem, + () -> logger.warn("Could not resolve item {} for {}", itemName, designator))); + } else { + resolve(itemMap, missingItemForDesignator, Designator::setItem); + } missingTopicMap.forEach((topic, parts) -> ParserUtils.createMqttTopic(topic, parts, this.root)); this.root.getMqttRoot().ensureCorrectPrefixes(); resolveList(channelMap, missingChannelListMap, Thing::addChannel); - resolveList(itemMap, missingItemLinkListMap, (channel, item) -> channel.addLink(new Link(item))); + 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(); @@ -87,7 +107,7 @@ public class EraserParserHelper { private void addItemToGroup(Group group, Item item) { groupedItems.add(item); - group.getItemList().insertChild(item, 0); + group.addItem(item); } private void fillUnused() { @@ -112,13 +132,13 @@ public class EraserParserHelper { sortedDanglingItems.add(item); } } - ParserUtils.createUnknownGroup(this.root, sortedDanglingItems); + ParserUtils.createUnknownGroup(this.root.getOpenHAB2Model(), sortedDanglingItems); } } private void createChannelCategories() { channelCategoryMap.values().stream().sorted(Comparator.comparing(this::ident)).forEach( - cc -> root.addChannelCategory(cc)); + cc -> root.getOpenHAB2Model().addChannelCategory(cc)); channelCategoryMap.clear(); } @@ -126,6 +146,7 @@ public class EraserParserHelper { Map<String, ItemCategory> newCategories = new HashMap<>(); missingItemCategoryMap.forEach((item, category) -> item.setCategory(newCategories.computeIfAbsent(category, ItemCategory::new))); + newCategories.values().forEach(node -> root.getOpenHAB2Model().addItemCategory(node)); } private void checkUnusedElements() { @@ -136,7 +157,7 @@ public class EraserParserHelper { if (elem instanceof ModelElement) { return ((ModelElement) elem).getID(); } else if (elem instanceof MqttTopic) { - return safeAllParts((MqttTopic) elem); + return ((MqttTopic) elem).getTopicString(); } else if (elem instanceof DefaultChannelCategory) { return ((DefaultChannelCategory) elem).getValue().name(); } else if (elem instanceof SimpleChannelCategory) { @@ -145,73 +166,73 @@ public class EraserParserHelper { return elem.toString(); } - private String safeAllParts(MqttTopic elem) { - StringBuilder sb = new StringBuilder(elem.getPart()); - ASTNode parent; - while (true) { - parent = elem.getParent(); - if (parent == null) break; - assert parent instanceof List; - parent = parent.getParent(); - if (parent == null || parent instanceof MqttRoot) { - break; - } - elem = (MqttTopic) parent; - sb.insert(0, elem.getPart() + "/"); - } - return sb.toString(); + private <Src extends ASTNode, Target extends ASTNode> void resolveList( + Map<String, Target> resolved, Map<Src, Iterable<String>> missing, BiConsumer<Src, Target> adder) { + missing.forEach( + (elem, keyList) -> keyList.forEach( + key -> resolve0(resolved, key, elem, adder))); + missing.clear(); } - private <Src extends ASTNode, Target extends ASTNode> void resolveList(Map<String, Target> resolved, Map<Src, Iterable<String>> missing, BiConsumer<Src, Target> adder) { - missing.forEach((elem, keyList) -> keyList.forEach(key -> { - Target value = resolved.get(key); - if (value == null) { - logger.warn("Reference in {} {} for '{}' cannot be resolved", - elem.getClass().getSimpleName(), ident(elem), key); - return; - } - if (checkUnusedElements) { - unusedElements.remove(value); - } - adder.accept(elem, value); - })); + private <Src extends ASTNode, Target extends ASTNode> void resolve( + Map<String, Target> resolved, Map<Src, String> missing, BiConsumer<Src, Target> setter) { + missing.forEach( + (elem, key) -> resolve0(resolved, key, elem, setter)); missing.clear(); } - private <Src extends ASTNode, Target extends ASTNode> void resolve(Map<String, Target> resolved, Map<Src, String> missing, BiConsumer<Src, Target> setter) { - missing.forEach((elem, key) -> { - Target value = resolved.get(key); - if (value == null) { - logger.warn("Reference in {} {} for '{}' cannot be resolved", - elem.getClass().getSimpleName(), ident(elem), key); - return; - } - if (checkUnusedElements) { - unusedElements.remove(value); - } - setter.accept(elem, value); - }); - missing.clear(); + private <Src extends ASTNode, Target extends ASTNode> void resolve0( + Map<String, Target> resolved, String key, Src elem, BiConsumer<Src, Target> action) { + Target value = resolved.get(key); + if (value == null) { + logger.warn("Reference in {} {} for '{}' cannot be resolved", + elem.getClass().getSimpleName(), ident(elem), key); + return; + } + if (checkUnusedElements) { + unusedElements.remove(value); + } + action.accept(elem, value); } + //--- Thing and ThingType --- + public Thing addThingType(Thing t, String typeName) { missingThingTypeMap.put(t, typeName); return t; } - public ChannelType setItemType(ChannelType ct, String itemTypeName) { - ct.setItemType(ItemType.valueOf(itemTypeName)); - return ct; + public ThingType setChannelTypes(ThingType tt, StringList channelTypeNames) { + missingChannelTypeListMap.put(tt, channelTypeNames); + return tt; } - public Item setCategory(Item item, String categoryName) { - missingItemCategoryMap.put(item, categoryName); - return item; + public ThingType setParameters(ThingType tt, StringList parameterNames) { + missingParameterListMap.put(tt, parameterNames); + return tt; } - public Item setTopic(Item item, String mqttTopicName) { - missingTopicMap.put(item, mqttTopicName); - return item; + public Thing setChannels(Thing t, StringList channelNames) { + missingChannelListMap.put(t, channelNames); + return t; + } + + public Thing setID(Thing thing, String id) { + thing.setID(id); + return thing; + } + + public ThingType setID(ThingType thingType, String id) { + thingType.setID(id); + thingTypeMap.put(id, thingType); + return thingType; + } + + //--- Channel and ChannelType --- + + public ChannelType setItemType(ChannelType ct, String itemTypeName) { + ct.setItemType(ItemType.valueOf(itemTypeName)); + return ct; } public ChannelType setChannelCategory(ChannelType ct, String name) { @@ -229,6 +250,95 @@ public class EraserParserHelper { return ct; } + public Channel setChannelType(Channel c, String channelTypeName) { + missingChannelTypeMap.put(c, channelTypeName); + return c; + } + + public Channel setLinks(Channel c, StringList linkNames) { + missingItemLinkListMap.put(c, linkNames); + return c; + } + + public ChannelType setID(ChannelType channelType, String id) { + channelType.setID(id); + channelTypeMap.put(id, channelType); + return channelType; + } + + public Channel setID(Channel channel, String id) { + channel.setID(id); + channelMap.put(id, channel); + return channel; + } + + //--- Item --- + + public Item createItem() { + ItemPrototype result = new ItemPrototype(); + result.disableSendState(); + return result; + } + + public Item setCategory(Item item, String categoryName) { + missingItemCategoryMap.put(item, categoryName); + return item; + } + + public Item setMetaData(Item item, StringKeyMap metaData) { + for (AbstractMap.SimpleEntry<String, String> entry : metaData) { + item.addMetaData(new ItemMetaData(entry.getKey(), entry.getValue())); + } + return item; + } + + public Item setControlling(Item item, StringList controlling) { + missingControllingListMap.put(item, controlling); + return item; + } + + public Item retype(Item itemWithCorrectType, Item prototype) { + itemWithCorrectType.setID(prototype.getID()); + itemWithCorrectType.setLabel(prototype.getLabel()); + itemWithCorrectType.setMetaDataList(prototype.getMetaDataList()); + if (!(itemWithCorrectType instanceof ActivityItem)) { + String state = prototype.getStateAsString(); + itemWithCorrectType.disableSendState(); + if (state.isEmpty()) { + itemWithCorrectType.setStateToDefault(); + } else { + itemWithCorrectType.setStateFromString(state); + } + itemWithCorrectType.enableSendState(); + } + + moveMissingForRetype(itemWithCorrectType, prototype, missingTopicMap); + moveMissingForRetype(itemWithCorrectType, prototype, missingControllingListMap); + moveMissingForRetype(itemWithCorrectType, prototype, missingItemCategoryMap); + + itemMap.put(prototype.getID(), itemWithCorrectType); + + itemOrder.add(itemWithCorrectType); + + return itemWithCorrectType; + } + + private <T> void moveMissingForRetype(Item itemWithCorrectType, Item prototype, Map<Item, T> missingXMap) { + T value = missingXMap.get(prototype); + if (value != null) { + missingXMap.put(itemWithCorrectType, value); + } + missingXMap.remove(prototype); + } + + public Item setID(Item item, String id) { + item.setID(id); + itemMap.put(id, item); + return item; + } + + //--- Group --- + public Group setSubGroups(Group g, StringList subGroupNames) { missingSubGroupListMap.put(g, subGroupNames); return g; @@ -252,10 +362,8 @@ public class EraserParserHelper { params.iterator().forEachRemaining(paramList::add); String param1, param2; if (paramList.size() == 2) { - // ATTENTION: Here the order is reversed, as the parser reads from back to front - // So assign first parameter to second read parameter, and vice versa - param1 = paramList.get(1); - param2 = paramList.get(0); + param1 = paramList.get(0); + param2 = paramList.get(1); } else { logger.error("Got {} instead of 2 parameters in group function {}!", paramList.size(), aggFunctionName); param1 = "?"; @@ -265,77 +373,77 @@ public class EraserParserHelper { return g; } - public ThingType setChannelTypes(ThingType tt, StringList channelTypeNames) { - missingChannelTypeListMap.put(tt, channelTypeNames); - return tt; + public Group setID(Group group, String id) { + group.setID(id); + groupMap.put(id, group); + return group; } + //--- Parameter --- + public Parameter setParameterValueType(Parameter p, String pvt) { - p.setType(ParameterValueType.valueOf(toTitleCase(pvt))); + p.setType(ParameterValueType.valueOf(JavaUtils.toTitleCase(pvt))); return p; } - private String toTitleCase(String s) { - if (s == null || s.isEmpty()) { - return s; - } - return s.substring(0, 1).toUpperCase() + s.substring(1); - } - public Parameter setDefault(Parameter p, String defaultValue) { p.setDefaultValue(new ParameterDefaultValue(defaultValue)); return p; } - public ThingType setParameters(ThingType tt, StringList parameterNames) { - missingParameterListMap.put(tt, parameterNames); - return tt; + public Parameter setID(Parameter parameter, String id) { + parameter.setID(id); + parameterMap.put(id, parameter); + return parameter; } - public Thing setChannels(Thing t, StringList channelNames) { - missingChannelListMap.put(t, channelNames); - return t; - } + //--- MQTT --- - public Channel setChannelType(Channel c, String channelTypeName) { - missingChannelTypeMap.put(c, channelTypeName); - return c; + public Item setTopic(Item item, String mqttTopicName) { + missingTopicMap.put(item, mqttTopicName); + return item; } - public Channel setLinks(Channel c, StringList linkNames) { - missingItemLinkListMap.put(c, linkNames); - return c; + //--- Activity --- + + public MachineLearningRoot setActivities(MachineLearningRoot mlr, IntegerKeyMap map) { + for (AbstractMap.SimpleEntry<Integer, String> entry : map) { + Activity activity = new Activity(); + activity.setIdentifier(entry.getKey()); + activity.setLabel(entry.getValue()); + mlr.addActivity(activity); + } + return mlr; } + //--- Root --- + public Root createRoot() { - this.root = new Root(); - this.root.setMqttRoot(new MqttRoot()); - this.root.setInfluxRoot(InfluxRoot.createDefault()); - this.root.setMachineLearningRoot(new MachineLearningRoot()); + this.root = Root.createEmptyRoot(); return this.root; } public Root createRoot(Thing t) { Root result = createRoot(); - result.addThing(t); + result.getOpenHAB2Model().addThing(t); return result; } public Root createRoot(Group g) { Root result = createRoot(); - result.addGroup(g); + result.getOpenHAB2Model().addGroup(g); return result; } public Root createRoot(ThingType tt) { Root result = createRoot(); - result.addThingType(tt); + result.getOpenHAB2Model().addThingType(tt); return result; } public Root createRoot(ChannelType ct) { Root result = createRoot(); - result.addChannelType(ct); + result.getOpenHAB2Model().addChannelType(ct); return result; } @@ -351,64 +459,30 @@ public class EraserParserHelper { return result; } - public Item createItem() { - return new ItemPrototype(); - } - - public Item retype(Item itemWithCorrectType, Item prototype) { - itemWithCorrectType.setID(prototype.getID()); - itemWithCorrectType.setLabel(prototype.getLabel()); - itemWithCorrectType.setStateFromString(prototype.getStateAsString()); - - missingTopicMap.put(itemWithCorrectType, missingTopicMap.get(prototype)); - missingTopicMap.remove(prototype); - - itemMap.put(prototype.getID(), itemWithCorrectType); - - itemOrder.add(itemWithCorrectType); - - return itemWithCorrectType; - } - - public Thing setID(Thing thing, String id) { - thing.setID(id); - return thing; - } - - public Item setID(Item item, String id) { - item.setID(id); - itemMap.put(id, item); - return item; - } - - public Group setID(Group group, String id) { - group.setID(id); - groupMap.put(id, group); - return group; - } - - public ThingType setID(ThingType thingType, String id) { - thingType.setID(id); - thingTypeMap.put(id, thingType); - return thingType; + public Root createRoot(MachineLearningRoot ml) { + Root result = createRoot(); + result.setMachineLearningRoot(ml); + return result; } - public Parameter setID(Parameter parameter, String id) { - parameter.setID(id); - parameterMap.put(id, parameter); - return parameter; + public Root createRoot(Rule rule) { + Root result = createRoot(); + result.addRule(rule); + return result; } - public ChannelType setID(ChannelType channelType, String id) { - channelType.setID(id); - channelTypeMap.put(id, channelType); - return channelType; + //+++ newStuff (to be categorized) +++ + public Designator createDesignator(String itemName) { + Designator result = new Designator(); + missingItemForDesignator.put(result, itemName); + return result; } - public Channel setID(Channel channel, String id) { - channel.setID(id); - channelMap.put(id, channel); - return channel; + public Rule createRule(Condition c, Action a) { + Rule result = new Rule(); + result.addCondition(c); + result.addAction(a); + return result; } } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/JavaUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/JavaUtils.java index 5a66f6a1d321480e3356c37c72f90998ad888d77..79e02fb74f53357b69b464e472801afbf311c3fd 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/JavaUtils.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/JavaUtils.java @@ -45,4 +45,11 @@ public class JavaUtils { return StreamSupport.stream(jastAddList.spliterator(), false); } + public static String toTitleCase(String s) { + if (s == null || s.isEmpty()) { + return s; + } + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + } 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 new file mode 100644 index 0000000000000000000000000000000000000000..a6c89a42bca8e978f837ad805ec4278634f3afa4 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java @@ -0,0 +1,305 @@ +package de.tudresden.inf.st.eraser.util; + +import de.tudresden.inf.st.eraser.jastadd.model.ASTNode; +import de.tudresden.inf.st.eraser.jastadd.model.ModelElement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Fluent API to pretty-print ASTNodes in a line-based fashion. + * + * @author rschoene - Initial contribution + */ +public class MemberPrinter { + + private static final Logger logger = LogManager.getLogger(MemberPrinter.class); + private static boolean addSemicolonNewlineWhenBuild = true; + private static boolean emitNothingOnEmptyBody = true; + private boolean empty = true; + private final StringBuilder sb; + + public enum ListBracketType { + SQUARE("[", "]"), + ROUND("(", ")"), + CURLY("{", "}"); + + private final String begin; + private final String end; + + ListBracketType(String begin, String end) { + this.begin = begin; + this.end = end; + } + } + + public MemberPrinter(String elementName) { + this.sb = new StringBuilder(); + if (elementName != null && !elementName.isEmpty()) { + sb.append(elementName).append(":"); + } + } + + public static void setAddSemicolonNewlineWhenBuild(boolean addSemicolonNewlineWhenBuild) { + MemberPrinter.addSemicolonNewlineWhenBuild = addSemicolonNewlineWhenBuild; + } + + public static void setEmitNothingOnEmptyBody(boolean emitNothingOnEmptyBody) { + MemberPrinter.emitNothingOnEmptyBody = emitNothingOnEmptyBody; + } + + public String build() { + if (empty) { + if (emitNothingOnEmptyBody) { + return ""; + } else { + logger.debug("Emitting empty body {}", sb); + } + } + if (addSemicolonNewlineWhenBuild) { + sb.append(" ;\n"); + } + return sb.toString(); + } + + /** + * Given a list of elements, concat the quoted id's of those elements, separated by commas, + * and appends it to the given StringBuilder. + * @param listOfElements the list of elements to concat + */ + private void concatIds(Iterable<? extends ModelElement> listOfElements) { + concatNodes(listOfElements, ModelElement::getID, true); + } + + /** + * Given a list of elements, concat the quoted id's of those elements, separated by commas, + * and appends it to the given StringBuilder. + * @param listOfNodes the list of nodes + * @param mapping a function to map a node to a ModelElement + */ + private <T extends ASTNode> void concatIds(Iterable<T> listOfNodes, + Function<T, ? extends ModelElement> mapping) { + concatNodes(listOfNodes, t -> mapping.apply(t).getID(), true); + } + + /** + * Given a list of nodes, apply a function on each of those, concat the output (separated by commas), + * and appends it to the given StringBuilder. + * @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, + Function<T, String> mapping, + boolean quote) { + boolean first = true; + for (T t : listOfNodes) { + if (first) { + first = false; + } else { + sb.append(", "); + } + if (quote) { + sb.append("\""); + } + sb.append(mapping.apply(t)); + if (quote) { + sb.append("\""); + } + } + } + + /** + * Appends model elements to the builder, if their number is positive. + * @param name The name of the member + * @param listOfElements The list of model elements + * @return this + */ + public MemberPrinter addIds(String name, List<? extends ModelElement> listOfElements) { + return addIds(name, listOfElements.size(), listOfElements); + } + + /** + * 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 + * @return this + */ + public MemberPrinter addIds(String name, int count, Iterable<? extends ModelElement> listOfElements) { + if (count > 0) { + sb.append(' ').append(name).append("=["); + concatIds(listOfElements); + sb.append("]"); + this.empty = false; + } + return this; + } + + /** + * 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, ? extends ModelElement> mapping) { + if (count > 0) { + sb.append(' ').append(name).append("=["); + concatIds(listOfNodes, mapping); + sb.append("]"); + this.empty = false; + } + return this; + } + + /** + * Applies the mapping to each node and appends the results to the builder, if count is positive. + * The list is enclosed with square brackets. + * @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 String + * @param <T> The type of all nodes + * @return this + */ + public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, + Function<T, String> mapping) { + return addNodes(name, count, listOfNodes, mapping, ListBracketType.SQUARE); + } + + /** + * Applies the mapping to each node and appends the results 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 String + * @param <T> The type of all nodes + * @param bracketType The type of brackets to enclose the list with + * @return this + */ + public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, + Function<T, String> mapping, ListBracketType bracketType) { + if (count > 0) { + sb.append(' ').append(name).append("=").append(bracketType.begin); + concatNodes(listOfNodes, mapping, false); + sb.append(bracketType.end); + this.empty = false; + } + return this; + } + + /** + * Appends a value to the builder, if the value is present. + * A function is used to get the value, such that it is only evaluated if the value is present + * @param name The name of the member + * @param isPresent Whether the optional value is present + * @param valueGetter A function to get the value + * @return this + */ + public MemberPrinter addOptional(String name, boolean isPresent, Supplier<String> valueGetter) { + if (isPresent) { + return add(name, valueGetter.get()); + } + return this; + } + + /** + * Appends the result of calling {@link ASTNode#prettyPrint()} for the child, if non-<code>null</code>. + * @param child The child to append + * @return this + */ + public MemberPrinter addOptionalPrettyPrint(ASTNode child) { + if (child != null) { + this.empty = false; + sb.append(child.prettyPrint()); + } + return this; + } + + /** + * Appends a the name of the flag, if it is set + * @param name The name of the member + * @param isSet Whether the flag is set + * @return this + */ + public MemberPrinter addFlag(String name, boolean isSet) { + if (isSet) { + this.empty = false; + sb.append(' ').append(name); + } + return this; + } + + /** + * Appends a value to the builder, if the value is not <code>null</code> and not empty + * @param name The name of the member + * @param actualValue The value to append + * @return this + */ + public MemberPrinter addNonDefault(String name, String actualValue) { + return addNonDefault(name, actualValue, ""); + } + + /** + * Appends a value to the builder, if the value is not the given default + * @param name The name of the member + * @param actualValue The value to append + * @param defaultValue The default value + * @return this + */ + public MemberPrinter addNonDefault(String name, Object actualValue, Object defaultValue) { + if (!actualValue.equals(defaultValue)) { + this.empty = false; + return add(name, actualValue); + } + return this; + } + + /** + * Appends a value to the builder, and issue a warning if it is either <code>null</code> or empty. + * @param name The name of the member + * @param actualValue The value to append + * @return this + */ + public MemberPrinter addRequired(String name, Object actualValue) { + if (actualValue == null || actualValue.toString().isEmpty()) { + logger.warn("Member \"{}\" not defined while printing", name); + actualValue = ""; + } + return add(name, actualValue); + } + + /** + * Appends a value to the builder, and issue a warning if it is either <code>null</code> or empty. + * @param name The name of the member + * @param objectValue An object to append + * @param mapping A function to get the real value from the object + * @return this + */ + public <T> MemberPrinter addRequired(String name, T objectValue, Function<T, String> mapping) { + String actualValue; + if (objectValue == null) { + logger.warn("Member \"{}\" not defined while printing", name); + actualValue = ""; + } else { + actualValue = mapping.apply(objectValue); + if (actualValue.isEmpty()) { + logger.warn("Member \"{}\" empty while printing", name); + } + } + return add(name, actualValue); + } + + private MemberPrinter add(String name, Object actualValue) { + this.empty = false; + sb.append(' ').append(name).append("=\"").append(actualValue).append("\""); + return this; + } + +} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MqttReceiver.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MqttReceiver.java index e433bf34d3b2e4848291a766ca3b5e1c55a80dc3..5fa9ad6800b3964369d0a1f140db6a4c3e0d164f 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MqttReceiver.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MqttReceiver.java @@ -1,6 +1,5 @@ package de.tudresden.inf.st.eraser.util; -import de.tudresden.inf.st.eraser.openhab2.mqtt.MQTTUpdater; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.fusesource.hawtbuf.Buffer; @@ -40,7 +39,7 @@ public class MqttReceiver implements AutoCloseable { private QoS qos; public MqttReceiver() { - this.logger = LogManager.getLogger(MQTTUpdater.class); + this.logger = LogManager.getLogger(MqttReceiver.class); this.readyLock = new ReentrantLock(); this.readyCondition = readyLock.newCondition(); this.ready = false; @@ -50,8 +49,8 @@ public class MqttReceiver implements AutoCloseable { /** * Sets the host to receive messages from */ - public void setHost(String host) { - this.host = URI.create("tcp://" + host + ":1883"); + public void setHost(String host, int port) { + this.host = URI.create("tcp://" + host + ":" + port); logger.debug("Host is {}", this.host); } @@ -134,6 +133,17 @@ public class MqttReceiver implements AutoCloseable { connection.connect(new Callback<Void>() { @Override public void onSuccess(Void value) { + connection.publish("components", "Eraser is listening".getBytes(), QoS.AT_LEAST_ONCE, false, new Callback<Void>() { + @Override + public void onSuccess(Void value) { + logger.debug("success sending welcome message"); + } + + @Override + public void onFailure(Throwable value) { + logger.debug("failure sending welcome message", value); + } + }); Topic[] topicArray = Arrays.stream(topics).map(topicName -> new Topic(topicName, qos)).toArray(Topic[]::new); logger.info("Connected, subscribing to {} topic(s) now.", topicArray.length); connection.subscribe(topicArray, new Callback<byte[]>() { @@ -151,7 +161,7 @@ public class MqttReceiver implements AutoCloseable { @Override public void onFailure(Throwable cause) { - logger.error("Could not subscribe, because {}", cause); + logger.error("Could not subscribe", cause); } }); } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java index e329f7928d587236342a04dfdd540fb1dcc2e4ea..716f63f9caa9462665b8819c9347aef466637a76 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java @@ -3,12 +3,10 @@ package de.tudresden.inf.st.eraser.util; import beaver.Parser; import beaver.Scanner; import beaver.Symbol; -import de.tudresden.inf.st.eraser.jastadd.model.Group; -import de.tudresden.inf.st.eraser.jastadd.model.Item; -import de.tudresden.inf.st.eraser.jastadd.model.MqttTopic; -import de.tudresden.inf.st.eraser.jastadd.model.Root; +import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser; import de.tudresden.inf.st.eraser.jastadd.scanner.EraserScanner; +import de.tudresden.inf.st.eraser.parser.EraserParserHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,8 +14,8 @@ import java.io.*; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Collection; +import java.util.Objects; /** * Utility methods involving scanner and parser of the models. @@ -26,9 +24,42 @@ import java.util.stream.Collectors; */ public class ParserUtils { + public static final String UNKNOWN_GROUP_NAME = "Unknown"; private static boolean verboseLoading = false; private static final Logger logger = LogManager.getLogger(ParserUtils.class); + private interface ReaderProvider { + Reader provide() throws IOException; + } + + private static class ReaderProviderByName implements ReaderProvider { + private final String filename; + private final Class<?> clazz; + + ReaderProviderByName(String filename, Class<?> clazz) { + this.filename = filename; + this.clazz = clazz; + } + + @Override + public Reader provide() throws IOException { + return getReader(filename, clazz); + } + } + + private static class ReaderProviderByURL implements ReaderProvider { + private final URL url; + + ReaderProviderByURL(URL url) { + this.url = url; + } + + @Override + public Reader provide() throws IOException { + return new InputStreamReader(url.openStream()); + } + } + /** * Print read tokens before loading the model. * This will effectively parse the input two times, thus slowing the operation. @@ -71,8 +102,23 @@ public class ParserUtils { */ public static Root load(String fileName, Class<?> clazz) throws IOException, Parser.Exception { logger.info("Loading model DSL file '{}'", fileName); + return load(new ReaderProviderByName(fileName, clazz)); + } - Reader reader = getReader(fileName, clazz); + /** + * Loads a model in a file from the given URL. + * @param url an URL pointing to a file + * @return the parsed model + * @throws IOException if the file could not be found, or opened + * @throws Parser.Exception if the file contains a malformed model + */ + public static Root load(URL url) throws IOException, Parser.Exception { + logger.info("Loading model DSL from '{}'", url); + return load(new ReaderProviderByURL(url)); + } + + private static Root load(ReaderProvider readerProvider) throws IOException, Parser.Exception { + Reader reader = readerProvider.provide(); if (verboseLoading) { EraserScanner scanner = new EraserScanner(reader); try { @@ -87,7 +133,7 @@ public class ParserUtils { try { reader.reset(); } catch (IOException resetEx) { - reader = getReader(fileName, clazz); + reader = readerProvider.provide(); } } } @@ -120,63 +166,91 @@ public class ParserUtils { } } - public static void createUnknownGroup(Root model, Collection<Item> danglingItems) { + /** + * Create well-known group call "Unknown" and add all dangling items to it. + * @param model The model to operate on + * @param danglingItems A list of items to add to the new group + */ + public static void createUnknownGroup(OpenHAB2Model model, Collection<Item> danglingItems) { Group unknownGroup = new Group(); - unknownGroup.setID("Unknown"); + unknownGroup.setID(UNKNOWN_GROUP_NAME); model.addGroup(unknownGroup); danglingItems.forEach(unknownGroup::addItem); - logger.warn("Creating group 'Unknown' for items: {}", PrinterUtils.concatIds(danglingItems)); + logger.info("Created new {}", unknownGroup.prettyPrint().trim()); } - public static void createMqttTopic(Item item, String topicName, Root root) { - String[] parts = topicName.split("/"); - String firstPart = parts[0]; - MqttTopic firstTopic = null; - for (MqttTopic topic : root.getMqttRoot().getTopicList()) { - if (topic.getPart().equals(firstPart)) { - firstTopic = topic; - break; - } - } - if (firstTopic == null) { - // no matching topic found for first part. create one. - firstTopic = createTopic(firstPart, root); - } -// MqttTopic firstTopic = firstPartTopicMap.computeIfAbsent(firstPart, part -> createTopic(part, root)); - MqttTopic lastTopic = processRemainingTopicParts(firstTopic, parts, 1); - item.setTopic(lastTopic); + /** + * Create a topic for the given topic name and assign it to the given item. + * @param item The item to which the topic will be assigned to + * @param topicSuffix The full topic name + * @param root The model to operate on + */ + public static void createMqttTopic(Item item, String topicSuffix, Root root) { + item.setTopic(root.getMqttRoot().resolveTopicSuffix(topicSuffix).orElseGet(() -> { + MqttTopic result = new MqttTopic(); + result.setTopicString(topicSuffix); + root.getMqttRoot().addTopic(result); + return result; + })); } - private static MqttTopic processRemainingTopicParts(MqttTopic topic, String[] parts, int index) { - if (index >= parts.length) { - return topic; - } - for (MqttTopic subTopic : topic.getSubTopicList()) { - if (subTopic.getPart().equals(parts[index])) { - // matching part found - return processRemainingTopicParts(subTopic, parts, index + 1); - } - } - // no matching part was found. create remaining topics. - for (int currentIndex = index; currentIndex < parts.length; currentIndex++) { - MqttTopic newTopic = createSubTopic(parts[currentIndex]); - topic.addSubTopic(newTopic); - topic = newTopic; - } - return topic; + public static NumberExpression parseNumberExpression(String expression_string) throws IOException, Parser.Exception { + return parseNumberExpression(expression_string, null); } - private static MqttTopic createSubTopic(String part) { - return createTopic(part, null); + public static LogicalExpression parseLogicalExpression(String expression_string) throws IOException, Parser.Exception { + return parseLogicalExpression(expression_string, null); } - private static MqttTopic createTopic(String part, Root root) { - MqttTopic result = new MqttTopic(); - result.setPart(part); - if (root != null) { - root.getMqttRoot().addTopic(result); + public static NumberExpression parseNumberExpression(String expression_string, Root root) throws IOException, Parser.Exception { + return (NumberExpression) parseExpression(expression_string, EraserParser.AltGoals.number_expression, root); + } + + public static LogicalExpression parseLogicalExpression(String expression_string, Root root) throws IOException, Parser.Exception { + return (LogicalExpression) parseExpression(expression_string, EraserParser.AltGoals.logical_expression, root); + } + + private static Expression parseExpression(String expression_string, short alt_goal, Root root) throws IOException, Parser.Exception { + EraserParserHelper.setInitialRoot(root); + StringReader reader = new StringReader(expression_string); + if (verboseLoading) { + EraserScanner scanner = new EraserScanner(reader); + try { + Symbol token; + while ((token = scanner.nextToken()).getId() != EraserParser.Terminals.EOF) { + logger.debug("start: {}, end: {}, id: {}, value: {}", + token.getStart(), token.getEnd(), EraserParser.Terminals.NAMES[token.getId()], token.value); + } + } catch (Scanner.Exception e) { + e.printStackTrace(); + } } + reader = new StringReader(expression_string); + EraserScanner scanner = new EraserScanner(reader); + EraserParser parser = new EraserParser(); + Expression result = (Expression) parser.parse(scanner, alt_goal); + parser.resolveReferences(); + reader.close(); + EraserParserHelper.setInitialRoot(null); return result; } + public static Item parseItem(String definition) + throws IllegalArgumentException, IOException, Parser.Exception { + StringReader reader = new StringReader(definition); + EraserScanner scanner = new EraserScanner(reader); + EraserParser parser = new EraserParser(); + Root root = (Root) parser.parse(scanner); + parser.resolveReferences(); + reader.close(); + int size = root.getOpenHAB2Model().items().size(); + if (size == 0) { + throw new IllegalArgumentException("Model does not contain any items!"); + } + if (size > 1) { + logger.warn("Model does contain {} items, ignoring all but the first.", size); + } + return root.getOpenHAB2Model().items().get(0); + } + } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/PrinterUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/PrinterUtils.java deleted file mode 100644 index 3f2b86613f5e627759a4c74cfa82dee58544b151..0000000000000000000000000000000000000000 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/PrinterUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.tudresden.inf.st.eraser.util; - -import de.tudresden.inf.st.eraser.jastadd.model.ASTNode; -import de.tudresden.inf.st.eraser.jastadd.model.JastAddList; -import de.tudresden.inf.st.eraser.jastadd.model.ModelElement; - -import java.util.function.Function; - -/** - * Helper methods shared by multiple non-terminals. - * - * @author rschoene - Initial contribution - */ -public class PrinterUtils { - - /** - * Given a list of elements, concat the quoted id's of those elements, separated by commas. - * @param listOfElements the list of elements to concat - * @return a comma-separated list of the ids of the given elements - */ - public static String concatIds(Iterable<? extends ModelElement> listOfElements) { - return concatIds(new StringBuilder(), listOfElements).toString(); - } - - /** - * Given a list of elements, concat the quoted id's of those elements, separated by commas, - * and appends it to the given StringBuilder. - * @param sb the StringBuilder to append to - * @param listOfElements the list of elements to concat - * @return the given StringBuilder - */ - public static StringBuilder concatIds(StringBuilder sb, Iterable<? extends ModelElement> listOfElements) { - boolean first = true; - for (ModelElement c : listOfElements) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append("\"").append(c.getID()).append("\""); - } - return sb; - } - - /** - * Given a list of elements, concat the quoted id's of those elements, separated by commas, - * and appends it to the given StringBuilder. - * @param sb the StringBuilder to append to - * @param listOfNodes the list of nodes - * @param mapping a function to map a node to a ModelElement - * @return the given StringBuilder - */ - public static <T extends ASTNode> StringBuilder concatIds(StringBuilder sb, Iterable<T> listOfNodes, - Function<T, ? extends ModelElement> mapping) { - boolean first = true; - for (T t : listOfNodes) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append("\"").append(mapping.apply(t).getID()).append("\""); - } - return sb; - } - -} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5f07ec7c576829975db82121e42e142497b49586 --- /dev/null +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java @@ -0,0 +1,64 @@ +package de.tudresden.inf.st.eraser.util; + +import de.tudresden.inf.st.eraser.jastadd.model.*; + +/** + * Helper class to create models used in tests. + * + * @author rschoene - Initial contribution + */ +public class TestUtils { + + public static class ModelAndItem { + public OpenHAB2Model model; + public NumberItem item; + static ModelAndItem of(OpenHAB2Model model, NumberItem item) { + ModelAndItem result = new ModelAndItem(); + result.model = model; + result.item = item; + return result; + } + } + + public static ModelAndItem createModelAndItem(double initialValue) { + return createModelAndItem(initialValue, false); + } + + public static ModelAndItem createModelAndItem(double initialValue, boolean useUpdatingItem) { + Root root = Root.createEmptyRoot(); + Group g = new Group(); + root.getOpenHAB2Model().addGroup(g); + g.setID("group1"); + + NumberItem item = addItemTo(g, initialValue, useUpdatingItem); + + return ModelAndItem.of(root.getOpenHAB2Model(), item); + } + + public static NumberItem addItemTo(OpenHAB2Model model, double initialValue) { + return addItemTo(model, initialValue, false); + } + + public static NumberItem addItemTo(OpenHAB2Model model, double initialValue, boolean useUpdatingItem) { + return addItemTo(getDefaultGroup(model), initialValue, useUpdatingItem); + } + + private static NumberItem addItemTo(Group group, double initialValue, boolean useUpdatingItem) { + NumberItem item = new NumberItem(); + if (useUpdatingItem) { + item.enableSendState(); + } else { + item.disableSendState(); + } + item.setID("item" + group.getNumItem()); + item.setState(initialValue, false); + group.addItem(item); + return item; + } + + public static Group getDefaultGroup(OpenHAB2Model model) { + // use first found group + return model.getGroup(0); + } + +} diff --git a/eraser-base/src/main/resources/log4j2.xml b/eraser-base/src/main/resources/log4j2.xml index 89799a2f09ba34d288e610d960b3ed6348213105..18175a02521156259c8789745fb849fa893302e9 100644 --- a/eraser-base/src/main/resources/log4j2.xml +++ b/eraser-base/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ControllingItemTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ControllingItemTest.java new file mode 100644 index 0000000000000000000000000000000000000000..edc98e72719ef63ffa63f0cb19aac9b7106ab879 --- /dev/null +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ControllingItemTest.java @@ -0,0 +1,127 @@ +package de.tudresden.inf.st.eraser; + +import de.tudresden.inf.st.eraser.util.TestUtils; +import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import org.junit.Assert; +import org.junit.Test; + +import java.util.function.Consumer; + +/** + * Testing the simple rule engine. + * + * @author rschoene - Initial contribution + */ +public class ControllingItemTest { + + private static final double DELTA = 0.001; + + @Test + public void testOneToOneNumber() { + ModelAndItem mai = TestUtils.createModelAndItem(0, true); + NumberItem item1 = mai.item; + NumberItem item2 = TestUtils.addItemTo(mai.model, 4, true); + item1.addControlling(item2); + + Assert.assertEquals(0, item1.getState(), DELTA); + Assert.assertEquals(4, item2.getState(), DELTA); + + item1.setState(5); + Assert.assertEquals(5, item1.getState(), DELTA); + Assert.assertEquals("Item was not controlled correctly", 5, item2.getState(), DELTA); + } + + @Test + public void testOneToManyNumber() { + ModelAndItem mai = TestUtils.createModelAndItem(0, true); + NumberItem item1 = mai.item; + NumberItem item2 = TestUtils.addItemTo(mai.model, 3, true); + NumberItem item3 = TestUtils.addItemTo(mai.model, 4, true); + NumberItem item4 = TestUtils.addItemTo(mai.model, 5, true); + item1.addControlling(item2); + item1.addControlling(item3); + item1.addControlling(item4); + + Assert.assertEquals(0, item1.getState(), DELTA); + Assert.assertEquals(3, item2.getState(), DELTA); + Assert.assertEquals(4, item3.getState(), DELTA); + Assert.assertEquals(5, item4.getState(), DELTA); + + item1.setState(5); + Assert.assertEquals(5, item1.getState(), DELTA); + Assert.assertEquals("Item2 was not controlled correctly", 5, item2.getState(), DELTA); + Assert.assertEquals("Item3 was not controlled correctly", 5, item3.getState(), DELTA); + Assert.assertEquals("Item4 was not controlled correctly", 5, item4.getState(), DELTA); + } + + @Test + public void testManyToOneColor() { + ModelAndItem mai = TestUtils.createModelAndItem(0); + OpenHAB2Model model = mai.model; + NumberItem numberItem = mai.item; + + Group g = TestUtils.getDefaultGroup(model); + + StringItem stringItem = initAndAddItem(g, new StringItem(), + item -> item.setState("0")); + + SwitchItem booleanItem = initAndAddItem(g, new SwitchItem(), + item -> item.setState(false)); + + ColorItem colorItem = initAndAddItem(g, new ColorItem(), + item -> item.setState(TupleHSB.of(0, 0, 0))); + + ColorItem target = initAndAddItem(g, new ColorItem(), + item -> item.setState(TupleHSB.of(0, 0, 0))); + + target.addControlledBy(numberItem); + target.addControlledBy(stringItem); + target.addControlledBy(booleanItem); + target.addControlledBy(colorItem); + + Assert.assertEquals(0, numberItem.getState(), DELTA); + Assert.assertEquals("0", stringItem.getState()); + Assert.assertFalse(booleanItem.getState()); + Assert.assertEquals(TupleHSB.of(0, 0, 0), colorItem.getState()); + Assert.assertEquals(TupleHSB.of(0, 0, 0), target.getState()); + + // number 5 -> set brightness to 5 + numberItem.setState(5); + Assert.assertEquals(5, numberItem.getState(), DELTA); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(0, 0, 5), target.getState()); + + // string 30 -> set brightness to 30 + stringItem.setState("30"); + Assert.assertEquals("30", stringItem.getState()); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(0, 0, 30), target.getState()); + + // string 30,20,10 -> set HSB to (30,20,10) + stringItem.setState("30,20,10"); + Assert.assertEquals("30,20,10", stringItem.getState()); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(30, 20, 10), target.getState()); + + // boolean true -> set brightness to full (100) + booleanItem.setState(true); + Assert.assertTrue(booleanItem.getState()); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(30, 20, 100), target.getState()); + + // color (33,33,33) -> set brightness to (33,33,33) + colorItem.setState(TupleHSB.of(33, 33, 33)); + Assert.assertEquals(TupleHSB.of(33, 33, 33), colorItem.getState()); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(33, 33, 33), target.getState()); + + // number 44 -> set brightness to 44 + numberItem.setState(44); + Assert.assertEquals(44, numberItem.getState(), DELTA); + Assert.assertEquals("Item was not controlled correctly", TupleHSB.of(33, 33, 44), target.getState()); + } + + private <T extends Item> T initAndAddItem(Group group, T item, Consumer<T> setState) { + item.setID("item" + group.getNumItem()); + group.addItem(item); + setState.accept(item); + item.enableSendState(); + return item; + } +} diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/DecisionTreeTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/DecisionTreeTest.java index 1cd643fdee35edc82810407e03a23e90f3c226eb..fa93e0691d782030d2e45c385f9327a57c9e1ae4 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/DecisionTreeTest.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/DecisionTreeTest.java @@ -1,6 +1,7 @@ package de.tudresden.inf.st.eraser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.TestUtils; import org.junit.Assert; import org.junit.Test; @@ -16,7 +17,7 @@ public class DecisionTreeTest { TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(4); DecisionTreeRoot dtroot = new DecisionTreeRoot(); - mai.model.getMachineLearningRoot().setActivityRecognition(dtroot); + mai.model.getRoot().getMachineLearningRoot().setActivityRecognition(dtroot); DecisionTreeLeaf isLessThanFour = newLeaf("less than four"); DecisionTreeLeaf isFourOrGreater = newLeaf("four or greater"); ItemStateNumberCheck check = new ItemStateNumberCheck(ComparatorType.LessThan, 4f); @@ -43,7 +44,7 @@ public class DecisionTreeTest { TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(20); DecisionTreeRoot dtroot = new DecisionTreeRoot(); - mai.model.getMachineLearningRoot().setActivityRecognition(dtroot); + mai.model.getRoot().getMachineLearningRoot().setActivityRecognition(dtroot); DecisionTreeLeaf isLessThan25 = newLeaf("less than 25"); DecisionTreeLeaf is25OrGreater = newLeaf("25 or greater"); @@ -129,7 +130,7 @@ public class DecisionTreeTest { TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0); DecisionTreeRoot dtroot = new DecisionTreeRoot(); - mai.model.getMachineLearningRoot().setActivityRecognition(dtroot); + mai.model.getRoot().getMachineLearningRoot().setActivityRecognition(dtroot); DecisionTreeLeaf left = newLeaf("left"); DecisionTreeLeaf right = newLeaf("right"); ItemStateNumberCheck check = new ItemStateNumberCheck(comparatorType, expected); diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionEvalTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionEvalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1758931346632a63c9f3297912b1fb7d1430da46 --- /dev/null +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionEvalTest.java @@ -0,0 +1,165 @@ +package de.tudresden.inf.st.eraser; + +import beaver.Parser; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.ParserUtils; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertThat; + +/** + * Test correct evaluation of NumberExpression and LogicalExpression. + * + * @author rschoene - Initial contribution + */ +public class ExpressionEvalTest { + + @Test + public void plusExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3 + 4)"); + assertThat(sut.eval(), equalTo(7.0)); + } + + @Test + public void minusExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(4.1 - 12.5)"); + assertThat(sut.eval(), equalTo(-8.4)); + } + + @Test + public void multExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3 * 4)"); + assertThat(sut.eval(), equalTo(12.0)); + } + + @Test + public void divExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(1.1 / 4.0)"); + assertThat(sut.eval(), equalTo(0.275)); + } + + @Test + public void powerExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(16 ^ 0.5)"); + assertThat(sut.eval(), equalTo(4.0)); + } + + @Test + public void parenthesizedExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3)"); + assertThat(sut.eval(), equalTo(3.0)); + } + + @Test + public void complexExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("((3 + 4) * (1 / (12 - 8)))"); + assertThat(sut.eval(), equalTo(1.75)); + MultExpression multExpression = (MultExpression) sut; + + // 3+4 + assertThat(multExpression.getLeftOperand(), instanceOf(AddExpression.class)); + assertThat(multExpression.getLeftOperand().eval(), equalTo(7.0)); + + // 1/(12-8) + assertThat(multExpression.getRightOperand(), instanceOf(DivExpression.class)); + DivExpression divExpression = (DivExpression) multExpression.getRightOperand(); + assertThat(divExpression.eval(), equalTo(0.25)); + + // 12-8 + assertThat(divExpression.getRightOperand().eval(), equalTo(4.0)); + } + + @Test + public void expressionWithItem() throws IOException, Parser.Exception { + double itemValue = 5.3; + Item referenceItem = ParserUtils.parseItem("Number Item: id=\"myItem\" state=\"" + itemValue + "\";"); + NumberExpression sut = ParserUtils.parseNumberExpression("(myItem * 3)", referenceItem.getRoot()); + assertThat(sut.eval(), equalTo(itemValue * 3)); + + // set item state to new value + itemValue = 17; + referenceItem.setStateFromDouble(itemValue); + assertThat(sut.eval(), equalTo(itemValue * 3)); + } + + @Test + public void comparingExpressions() throws IOException, Parser.Exception { + comparingExpression(1, "<", 2, true); + comparingExpression(2, "<", 2, false); + comparingExpression(3, "<", 2, false); + + comparingExpression(3, "<=", 4, true); + comparingExpression(4, "<=", 4, true); + comparingExpression(5, "<=", 4, false); + + comparingExpression(5, "==", 6, false); + comparingExpression(6, "==", 6, true); + + comparingExpression(7, "!=", 8, true); + comparingExpression(8, "!=", 8, false); + + comparingExpression(9, ">=", 10, false); + comparingExpression(10, ">=", 10, true); + comparingExpression(11, ">=", 10, true); + + comparingExpression(11,">", 12, false); + comparingExpression(12,">", 12, false); + comparingExpression(13,">", 12, true); + } + + private void comparingExpression(double left, String actualComparatorString, double right, boolean expectedResult) throws IOException, Parser.Exception { + String expression = String.format("(%s %s %s)", Double.toString(left), actualComparatorString, Double.toString(right)); + LogicalExpression sut = ParserUtils.parseLogicalExpression(expression); + assertThat(sut.eval(), equalTo(expectedResult)); + } + + @Test + public void notExpression() throws IOException, Parser.Exception { + LogicalExpression sut = ParserUtils.parseLogicalExpression("!(0==0)"); + assertThat(sut.eval(), equalTo(false)); + LogicalExpression sut2 = ParserUtils.parseLogicalExpression("!!(0==0)"); + assertThat(sut2.eval(), equalTo(true)); + } + + @Test + public void andExpression() throws IOException, Parser.Exception { + LogicalExpression sut = ParserUtils.parseLogicalExpression("((0==0) & (0==0))"); + assertThat(sut.eval(), equalTo(true)); + + LogicalExpression sut2 = ParserUtils.parseLogicalExpression("((0==0) & (0==1))"); + assertThat(sut2.eval(), equalTo(false)); + + LogicalExpression sut3 = ParserUtils.parseLogicalExpression("((0==1) & (0==0))"); + assertThat(sut3.eval(), equalTo(false)); + + LogicalExpression sut4 = ParserUtils.parseLogicalExpression("((0==1) & (0==1))"); + assertThat(sut4.eval(), equalTo(false)); + } + + @Test + public void orExpression() throws IOException, Parser.Exception { + LogicalExpression sut = ParserUtils.parseLogicalExpression("((0==0) | (0==0))"); + assertThat(sut.eval(), equalTo(true)); + + LogicalExpression sut2 = ParserUtils.parseLogicalExpression("((0==0) | (0==1))"); + assertThat(sut2.eval(), equalTo(true)); + + LogicalExpression sut3 = ParserUtils.parseLogicalExpression("((0==1) | (0==0))"); + assertThat(sut3.eval(), equalTo(true)); + + LogicalExpression sut4 = ParserUtils.parseLogicalExpression("((0==1) | (0==1))"); + assertThat(sut4.eval(), equalTo(false)); + } + + @Test + public void parenthesizedLogicalExpression() throws IOException, Parser.Exception { + LogicalExpression sut = ParserUtils.parseLogicalExpression("((0==0))"); + assertThat(sut.eval(), equalTo(true)); + ParenthesizedLogicalExpression parenthesizedLogicalExpression = (ParenthesizedLogicalExpression) sut; + assertThat(parenthesizedLogicalExpression.getOperand().eval(), equalTo(true)); + } +} diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a034d3c8f8699046c9c89d2f139f14880a80b704 --- /dev/null +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java @@ -0,0 +1,238 @@ +package de.tudresden.inf.st.eraser; + +import beaver.Parser; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.ParserUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertThat; + +/** + * Test correct parsing of NumberExpression and LogicalExpression. + * + * @author rschoene - Initial contribution + */ +public class ExpressionParserTest { + + private static final String TRUE_EXPRESSION = "(0.0==0.0)"; + + @Test + public void plusExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(3 + 4)"); + assertThat(sut, instanceOf(AddExpression.class)); + AddExpression addExpression = (AddExpression) sut; + assertThat(addExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) addExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(addExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) addExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.0)); + } + + @Test + public void minusExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(12.5 - 4.1)"); + assertThat(sut, instanceOf(SubExpression.class)); + SubExpression subExpression = (SubExpression) sut; + assertThat(subExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) subExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(12.5)); + assertThat(subExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) subExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.1)); + } + + @Test + public void multExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(0 * 0)"); + assertThat(sut, instanceOf(MultExpression.class)); + MultExpression multExpression = (MultExpression) sut; + assertThat(multExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) multExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(0.0)); + assertThat(multExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) multExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.0)); + } + + @Test + public void divExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(1.1 / 0.0)"); + assertThat(sut, instanceOf(DivExpression.class)); + DivExpression divExpression = (DivExpression) sut; + assertThat(divExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) divExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(1.1)); + assertThat(divExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) divExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.0)); + } + + @Test + public void powerExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(3 ^ 0.5)"); + assertThat(sut, instanceOf(PowerExpression.class)); + PowerExpression powExpression = (PowerExpression) sut; + assertThat(powExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) powExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(powExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) powExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.5)); + } + + @Test + public void parenthesizedExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("(3)"); + assertThat(sut, instanceOf(ParenthesizedNumberExpression.class)); + ParenthesizedNumberExpression parenExpression = (ParenthesizedNumberExpression) sut; + assertThat(parenExpression.getOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) parenExpression.getOperand(); + assertThat(left.getValue(), equalTo(3.0)); + } + + @Test + public void complexExpression() throws IOException, Parser.Exception { + NumberExpression sut = parseWithRoundTripNumberExpression("((3 + 4) * (1 / (12 - 8)))"); + assertThat(sut, instanceOf(MultExpression.class)); + MultExpression multExpression = (MultExpression) sut; + + // 3+4 + assertThat(multExpression.getLeftOperand(), instanceOf(AddExpression.class)); + AddExpression addExpression = (AddExpression) multExpression.getLeftOperand(); + assertThat(addExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) addExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(addExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) addExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.0)); + + // 1/(12-8) + assertThat(multExpression.getRightOperand(), instanceOf(DivExpression.class)); + DivExpression divExpression = (DivExpression) multExpression.getRightOperand(); + + assertThat(divExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftOfDiv = (NumberLiteralExpression) divExpression.getLeftOperand(); + assertThat(leftOfDiv.getValue(), equalTo(1.0)); + + // 12-8 + assertThat(divExpression.getRightOperand(), instanceOf(SubExpression.class)); + SubExpression rightofDiv = (SubExpression) divExpression.getRightOperand(); + assertThat(rightofDiv.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftOfSSub = (NumberLiteralExpression) rightofDiv.getLeftOperand(); + assertThat(leftOfSSub.getValue(), equalTo(12.0)); + assertThat(rightofDiv.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression rightOfSub = (NumberLiteralExpression) rightofDiv.getRightOperand(); + assertThat(rightOfSub.getValue(), equalTo(8.0)); + } + + @Test + public void expressionWithItem() throws IOException, Parser.Exception { + Item referenceItem = ParserUtils.parseItem("Number Item: id=\"myItem\";"); + NumberExpression sut = parseWithRoundTripNumberExpression("(myItem * 3)", referenceItem.getRoot()); + assertThat(sut, instanceOf(MultExpression.class)); + MultExpression multExpression = (MultExpression) sut; + assertThat(multExpression.getLeftOperand(), instanceOf(Designator.class)); + Designator left = (Designator) multExpression.getLeftOperand(); + assertThat(left.getItem(), equalTo(referenceItem)); + assertThat(multExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) multExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(3.0)); + } + + @Test + public void comparingExpressions() throws IOException, Parser.Exception { + comparingExpression("<", ComparatorType.LessThan, 1, 2); + comparingExpression("<=", ComparatorType.LessOrEqualThan, 3, 4); + comparingExpression("==", ComparatorType.Equals, 5, 6); + comparingExpression("!=", ComparatorType.NotEquals, 7, 8); + comparingExpression(">=", ComparatorType.GreaterOrEqualThan, 9, 10); + comparingExpression(">", ComparatorType.GreaterThan, 11, 12); + } + + private void comparingExpression(String actualComparatorString, ComparatorType expectedComparatorType, double left, double right) throws IOException, Parser.Exception { + String expression = String.format("(%s %s %s)", Double.toString(left), actualComparatorString, Double.toString(right)); + LogicalExpression sut = parseWithRoundTripLogicalExpression(expression); + assertThat(sut, instanceOf(ComparingExpression.class)); + ComparingExpression comparingExpression = (ComparingExpression) sut; + assertThat(comparingExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftExpression = (NumberLiteralExpression) comparingExpression.getLeftOperand(); + assertThat(leftExpression.getValue(), equalTo(left)); + assertThat(comparingExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression rightExpression = (NumberLiteralExpression) comparingExpression.getRightOperand(); + assertThat(rightExpression.getValue(), equalTo(right)); + assertThat(comparingExpression.getComparator(), equalTo(expectedComparatorType)); + } + + @Test + public void notExpression() throws IOException, Parser.Exception { + LogicalExpression sut = parseWithRoundTripLogicalExpression("!" + TRUE_EXPRESSION); + assertThat(sut, instanceOf(NotExpression.class)); + NotExpression notExpression = (NotExpression) sut; + checkZeroEqualsZero(notExpression.getOperand()); + } + + @Test + public void andExpression() throws IOException, Parser.Exception { + LogicalExpression sut = parseWithRoundTripLogicalExpression("(" + TRUE_EXPRESSION + " & " + TRUE_EXPRESSION + ")"); + assertThat(sut, instanceOf(AndExpression.class)); + AndExpression notExpression = (AndExpression) sut; + checkZeroEqualsZero(notExpression.getLeftOperand()); + checkZeroEqualsZero(notExpression.getRightOperand()); + } + + @Test + public void orExpression() throws IOException, Parser.Exception { + LogicalExpression sut = parseWithRoundTripLogicalExpression("(" + TRUE_EXPRESSION + " | " + TRUE_EXPRESSION + ")"); + assertThat(sut, instanceOf(OrExpression.class)); + OrExpression notExpression = (OrExpression) sut; + checkZeroEqualsZero(notExpression.getLeftOperand()); + checkZeroEqualsZero(notExpression.getRightOperand()); + } + + @Test + public void parenthesizedLogicalExpression() throws IOException, Parser.Exception { + LogicalExpression sut = parseWithRoundTripLogicalExpression("(" + TRUE_EXPRESSION + ")"); + assertThat(sut, instanceOf(ParenthesizedLogicalExpression.class)); + ParenthesizedLogicalExpression parenthesizedLogicalExpression = (ParenthesizedLogicalExpression) sut; + checkZeroEqualsZero(parenthesizedLogicalExpression.getOperand()); + } + + private NumberExpression parseWithRoundTripNumberExpression(String numberExpression) throws IOException, Parser.Exception { + return parseWithRoundTripNumberExpression(numberExpression, null); + } + + private NumberExpression parseWithRoundTripNumberExpression(String numberExpression, Root root) throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression(numberExpression, root); + String first = sut.prettyPrint(); + NumberExpression reParsed = ParserUtils.parseNumberExpression(first, root); + String second = reParsed.prettyPrint(); + assertThat(first, equalTo(second)); + return sut; + } + + private LogicalExpression parseWithRoundTripLogicalExpression(String logicalExpression) throws IOException, Parser.Exception { + LogicalExpression sut = ParserUtils.parseLogicalExpression(logicalExpression); + String first = sut.prettyPrint(); + LogicalExpression reParsed = ParserUtils.parseLogicalExpression(first); + String second = reParsed.prettyPrint(); + assertThat(first, equalTo(second)); + return sut; + } + + private void checkZeroEqualsZero(LogicalExpression logicalExpression) { + assertThat(logicalExpression, instanceOf(ComparingExpression.class)); + ComparingExpression comparingExpression = (ComparingExpression) logicalExpression; + assertThat(comparingExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftOfSSub = (NumberLiteralExpression) comparingExpression.getLeftOperand(); + assertThat(leftOfSSub.getValue(), equalTo(0.0)); + assertThat(comparingExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression rightOfSub = (NumberLiteralExpression) comparingExpression.getRightOperand(); + assertThat(rightOfSub.getValue(), equalTo(0.0)); + } +} diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/InfluxTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/InfluxTest.java index 5707ad2eeb2e516626f7608c159046e84c3ec6c5..9526a4db04802684261f820e807db35d6a47c24f 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/InfluxTest.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/InfluxTest.java @@ -1,10 +1,12 @@ package de.tudresden.inf.st.eraser; -import de.tudresden.inf.st.eraser.TestUtils.ModelAndItem; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.TestUtils; +import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.testcontainers.containers.InfluxDBContainer; import java.time.Instant; import java.util.ArrayList; @@ -28,6 +30,12 @@ public class InfluxTest { private ModelAndItem mai; + @ClassRule + public static InfluxDBContainer influxDbContainer = new InfluxDBContainer() + .withDatabase(InfluxRoot.createDefault().getDbName()) + .withAdmin(InfluxRoot.createDefault().getUser()) + .withAdminPassword(InfluxRoot.createDefault().getPassword()); + @Test public void oneItem() { NumberItem item = mai.item; @@ -94,8 +102,8 @@ public class InfluxTest { @Test public void justAdapter() { - InfluxAdapter influxAdapter = mai.model.getInfluxRoot().influxAdapter(); - Assume.assumeTrue("Adapter not connected", influxAdapter.isConnected()); + InfluxAdapter influxAdapter = getInfluxRoot().influxAdapter(); + Assert.assertTrue("Adapter not connected", influxAdapter.isConnected()); influxAdapter.deleteDatabase(); // write one point @@ -146,16 +154,21 @@ public class InfluxTest { point -> points.add((DoubleStatePoint) point)); } else { influxRoot = InfluxRoot.createDefault(); - // use zih vm2098 running influx + // use container running influx influxRoot.setDbName(InfluxTest.class.getSimpleName()); - influxRoot.setHostByName("vm2098.zih.tu-dresden.de"); + System.out.println("ports: " + influxDbContainer.getPortBindings() + " url: '" + influxDbContainer.getUrl() + "'"); + influxRoot.setHostByName(influxDbContainer.getUrl().replaceAll("^http://", "")); } - mai.model.setInfluxRoot(influxRoot); + mai.model.getRoot().setInfluxRoot(influxRoot); Assume.assumeTrue(influxRoot.influxAdapter().isConnected()); influxRoot.influxAdapter().deleteDatabase(); return mai; } + private InfluxRoot getInfluxRoot() { + return mai.model.getRoot().getInfluxRoot(); + } + private List<DoubleStatePoint> query(ItemWithDoubleState... allItems) { if (useStub) { return points; @@ -171,13 +184,13 @@ public class InfluxTest { @Before public void setNewModel() { mai = createModel(); - mai.model.getInfluxRoot().influxAdapter().diableAsyncQuery(); + getInfluxRoot().influxAdapter().disableAsyncQuery(); } @After public void closeInfluxAdapter() throws Exception { if (mai != null && mai.model != null) { - mai.model.getInfluxRoot().influxAdapter().close(); + getInfluxRoot().influxAdapter().close(); } } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ItemTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ItemTests.java index 0b02f08b9d6c12e2ac51935d2c1da8bb7660c5e6..ffda213bea9a359097db8b962f03a48e52502c57 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ItemTests.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ItemTests.java @@ -1,9 +1,12 @@ package de.tudresden.inf.st.eraser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.TestUtils; +import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; import org.junit.Assert; import org.junit.Test; +import java.time.Instant; import java.util.Date; /** @@ -15,7 +18,7 @@ public class ItemTests { @Test public void testItemWithBooleanStateEquals() { - ItemWithBooleanState sut = new SwitchItem(); + ItemWithBooleanState sut = createItem(SwitchItem::new); sut.setState(true); Assert.assertTrue("State 'true' should match 'true'", sut.stateEquals(true)); @@ -28,7 +31,7 @@ public class ItemTests { @Test public void testItemWithStringStateEquals() { - ItemWithStringState sut = new ImageItem(); + ItemWithStringState sut = createItem(ImageItem::new); sut.setState("correct"); Assert.assertTrue("State 'correct' should match 'correct'", @@ -39,7 +42,7 @@ public class ItemTests { @Test public void testItemWithDoubleStateEquals() { - ItemWithDoubleState sut = new NumberItem(); + ItemWithDoubleState sut = createItem(NumberItem::new); sut.setState(3.0); Assert.assertTrue("State '3.0' should match '3.0'", sut.stateEquals(3.0)); @@ -52,7 +55,7 @@ public class ItemTests { @Test public void testItemWithTupleHSBStateEquals() { - ColorItem sut = new ColorItem(); + ColorItem sut = createItem(ColorItem::new); sut.setState(TupleHSB.of(1, 2, 3)); Assert.assertTrue("State 'TupleHSB(1,2,3)' should match 'TupleHSB(1,2,3)'", @@ -71,73 +74,27 @@ public class ItemTests { @Test public void testItemWithDateStateEquals() { - DateTimeItem sut = new DateTimeItem(); + DateTimeItem sut = createItem(DateTimeItem::new); - sut.setState(new Date(1543415826)); + sut.setState(Instant.ofEpochMilli(1543415826)); Assert.assertTrue("State 'Date(1543415826)' should match 'Date(1543415826)'", - sut.stateEquals(new Date(1543415826))); + sut.stateEquals(Instant.ofEpochMilli(1543415826))); Assert.assertFalse("State 'Date(1543415826)' should not match 'Date(4)'", - sut.stateEquals(new Date(4))); + sut.stateEquals(Instant.ofEpochMilli(4))); } - @Test - public void testItemWithBooleanCopyEquals() { - ItemWithBooleanState sut = new SwitchItem(); - - sut.setState(true); - Object copiedState = sut.copyState(); - Assert.assertTrue("State 'true' should match copy", sut.stateEquals(copiedState)); - - sut.setState(false); - Assert.assertFalse("State 'false' should not match copy", sut.stateEquals(copiedState)); - } - - @Test - public void testItemWithStringCopyEquals() { - ItemWithStringState sut = new ImageItem(); - - sut.setState("correct"); - Object copiedState = sut.copyState(); - Assert.assertTrue("State 'correct' should match copy", sut.stateEquals(copiedState)); - - sut.setState("something else"); - Assert.assertFalse("State 'something else' should not match copy", sut.stateEquals(copiedState)); + @FunctionalInterface + private interface CreateItem<T extends Item> { + T create(); } - @Test - public void testItemWithLongCopyEquals() { - ItemWithDoubleState sut = new NumberItem(); - - sut.setState(3.0); - Object copiedState = sut.copyState(); - Assert.assertTrue("State '3.0' should match copy", sut.stateEquals(copiedState)); - - sut.setState(4.0); - Assert.assertFalse("State '4.0' should not match copy", sut.stateEquals(copiedState)); - } - - @Test - public void testItemWithTupleHSBCopyEquals() { - ColorItem sut = new ColorItem(); - - sut.setState(TupleHSB.of(1, 2, 3)); - Object copiedState = sut.copyState(); - Assert.assertTrue("State 'TupleHSB(1,2,3)' should match copy", sut.stateEquals(copiedState)); - - sut.setState(TupleHSB.of(5,5,5)); - Assert.assertFalse("State 'TupleHSB(5,5,5)' should not match copy", sut.stateEquals(copiedState)); - } - - @Test - public void testItemWithDateCopyEquals() { - DateTimeItem sut = new DateTimeItem(); - - sut.setState(new Date(1543415826)); - Object copiedState = sut.copyState(); - Assert.assertTrue("State 'Date(1543415826' should match copy", sut.stateEquals(copiedState)); - - sut.setState(new Date(4)); - Assert.assertFalse("State 'Date(4)' should not match copy", sut.stateEquals(copiedState)); + private <T extends Item> T createItem(CreateItem<T> creator) { + // create a root with default group and one unused item + ModelAndItem mai = TestUtils.createModelAndItem(0); + // create wanted item, add it to default group, and return it + T result = creator.create(); + TestUtils.getDefaultGroup(mai.model).addItem(result); + return result; } } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java index 53c420a3023e689cfcef2a58816ddf74b2767ce9..abcbd203be180e81b766bc48aa66cdb7778efae7 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java @@ -13,8 +13,8 @@ import org.junit.runners.Parameterized; * * @author rschoene - Initial contribution */ -@RunWith(ParallelParameterized.class) -//@RunWith(Parameterized.class) +//@RunWith(ParallelParameterized.class) +@RunWith(Parameterized.class) public class MarshallingTests { private static final TestProperties properties = new TestProperties(); diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MqttTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MqttTests.java index 73520f3f406474d04ebe2d93db2a6042702a4ee0..28a8d664cd88f53495a7049b065f7ef206230e39 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MqttTests.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MqttTests.java @@ -2,11 +2,17 @@ package de.tudresden.inf.st.eraser; import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.util.MqttReceiver; +import de.tudresden.inf.st.eraser.util.TestUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.junit.Assert; import org.junit.Assume; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; import java.io.IOException; import java.util.ArrayList; @@ -34,25 +40,29 @@ public class MqttTests { private static final double thirdState = 3.0; private List<String> messages = new ArrayList<>(); + private static Logger logger = LogManager.getLogger(MqttTests.class); + @ClassRule + public static GenericContainer mqttBroker = new GenericContainer<>("eclipse-mosquitto:1.5") + .withExposedPorts(1883); @Test public void resolve1() { - RootItemAndTwoTopics rootAB = createAB(); - MqttRoot sut = rootAB.model.getMqttRoot(); + ModelItemAndTwoTopics modelAB = createAB(); + MqttRoot sut = modelAB.model.getRoot().getMqttRoot(); // incoming mqtt topic might be "inc/a/" or "inc/a/b" Assert.assertTrue(sut.resolveTopic("inc/a").isPresent()); - Assert.assertEquals("Could not resolve a.", rootAB.firstTopic, sut.resolveTopic("inc/a").get()); + Assert.assertEquals("Could not resolve a.", modelAB.firstTopic, sut.resolveTopic("inc/a").get()); Assert.assertTrue(sut.resolveTopic("inc/a/b").isPresent()); - Assert.assertEquals("Could not resolve a/b.", rootAB.secondTopic, sut.resolveTopic("inc/a/b").get()); + Assert.assertEquals("Could not resolve a/b.", modelAB.secondTopic, sut.resolveTopic("inc/a/b").get()); } @Test public void brokerConnected() throws Exception { - RootItemAndTwoTopics rootAB = createAB(); - MqttRoot sut = rootAB.model.getMqttRoot(); + ModelItemAndTwoTopics modelAB = createAB(); + MqttRoot sut = modelAB.model.getRoot().getMqttRoot(); // MqttRoot mqttRoot = new MqttRoot(); // mqttRoot.setHostByName("localhost"); // MQTTSender sender = new MQTTSenderImpl().setHost(mqttRoot.getHost()); @@ -66,59 +76,46 @@ public class MqttTests { public void itemUpdateSend1() throws Exception { String expectedTopic = outgoingPrefix + "/" + firstPart + "/" + secondPart; - RootItemAndTwoTopics rootAB = createAB(); - assumeSenderConnected(rootAB.model); + ModelItemAndTwoTopics modelAB = createAB(); + assertSenderConnected(modelAB); - NumberItem sut = rootAB.item; - sut.setTopic(rootAB.secondTopic); + NumberItem sut = modelAB.item; + sut.setTopic(modelAB.secondTopic); createMqttReceiver(expectedTopic); sut.setState(firstState); - sut.sendState(); assertTimeoutEquals(2, 1, messages::size); Assert.assertEquals(Double.toString(firstState), messages.get(0)); sut.setState(secondState); - sut.sendState(); assertTimeoutEquals(2, 2, messages::size); Assert.assertEquals(Double.toString(secondState), messages.get(1)); } - private void assumeSenderConnected(Root model) { - Assume.assumeTrue("Broker is not connected", model.getMqttRoot().getMqttSender().isConnected()); - } - @Test public void itemUpdateSend2() throws Exception { String expectedTopic1 = outgoingPrefix + "/" + firstPart + "/" + secondPart; String expectedTopic2 = outgoingPrefix + "/" + alternativeFirstPart + "/" + secondPart; - RootItemAndTwoTopics rootAB = createAB(); - assumeSenderConnected(rootAB.model); + ModelItemAndTwoTopics modelAB = createAB(); + assertSenderConnected(modelAB); - NumberItem item1 = rootAB.item; - item1.setTopic(rootAB.secondTopic); + NumberItem item1 = modelAB.item; + item1.setTopic(modelAB.secondTopic); - MqttTopic alternative = new MqttTopic(); - alternative.setPart(alternativeFirstPart); - MqttTopic alternativeB = new MqttTopic(); - alternativeB.setPart(secondPart); - alternative.addSubTopic(alternativeB); - rootAB.model.getMqttRoot().addTopic(alternative); + MqttTopic alternativeB = createAndAddMqttTopic(modelAB.model.getRoot().getMqttRoot(),alternativeFirstPart + "/" + secondPart); - NumberItem item2 = TestUtils.addItemTo(rootAB.model, 0); + NumberItem item2 = TestUtils.addItemTo(modelAB.model, 0, true); item2.setTopic(alternativeB); createMqttReceiver(expectedTopic1, expectedTopic2); item1.setState(firstState); - item1.sendState(); item2.setState(firstState); - item2.sendState(); assertTimeoutEquals(2, 2, messages::size); Assert.assertEquals(Double.toString(firstState), messages.get(0)); @@ -126,8 +123,6 @@ public class MqttTests { item1.setState(secondState); item2.setState(thirdState); - item1.sendState(); - item2.sendState(); assertTimeoutEquals(3, 4, messages::size); // TODO actually this does not test, whether the topic was correct for each state @@ -135,6 +130,19 @@ public class MqttTests { Assert.assertThat(messages, hasItem(Double.toString(thirdState))); } + private void assertSenderConnected(ModelItemAndTwoTopics modelAB) { + MqttRoot mqttRoot = modelAB.model.getRoot().getMqttRoot(); + mqttBroker.waitingFor(Wait.forHealthcheck()); + if (!mqttRoot.getMqttSender().isConnected()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.catching(e); + } + } + Assert.assertTrue("Broker is not connected", mqttRoot.getMqttSender().isConnected()); + } + private void createMqttReceiver(String... expectedTopics) throws IOException { if (useStub) { // do not need receiver, as messages are directly written by MqttSenderStub and callback @@ -142,7 +150,8 @@ public class MqttTests { } MqttReceiver receiver = new MqttReceiver(); List<String> expectedTopicList = Arrays.asList(expectedTopics); - receiver.setHost("localhost"); +// receiver.setHost("localhost", 1883); + receiver.setHost(mqttBroker.getContainerIpAddress(), mqttBroker.getFirstMappedPort()); receiver.setTopicsForSubscription(expectedTopics); receiver.setOnMessage((topic, message) -> { Assert.assertThat(expectedTopicList, hasItem(topic)); @@ -152,9 +161,9 @@ public class MqttTests { receiver.waitUntilReady(2, TimeUnit.SECONDS); } - private RootItemAndTwoTopics createAB() { - TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0); - Root model = mai.model; + private ModelItemAndTwoTopics createAB() { + TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0, true); + OpenHAB2Model model = mai.model; MqttRoot mqttRoot = new MqttRoot(); mqttRoot.setIncomingPrefix("inc"); mqttRoot.setOutgoingPrefix(outgoingPrefix); @@ -163,26 +172,23 @@ public class MqttTests { // now a SenderStub is being used ((MQTTSenderStub) mqttRoot.getMqttSender()).setCallback(((topic, message, qos) -> messages.add(message))); } else { - mqttRoot.setHostByName("localhost"); +// mqttRoot.setHostByName("localhost"); + mqttRoot.setHost(ExternalHost.of(mqttBroker.getContainerIpAddress(), mqttBroker.getFirstMappedPort())); } - MqttTopic a = new MqttTopic(); - a.setPart(firstPart); - MqttTopic ab = new MqttTopic(); - ab.setPart(secondPart); - a.addSubTopic(ab); - mqttRoot.addTopic(a); + MqttTopic a = createAndAddMqttTopic(mqttRoot, firstPart); + MqttTopic ab = createAndAddMqttTopic(mqttRoot, firstPart + "/" + secondPart); mqttRoot.ensureCorrectPrefixes(); - model.setMqttRoot(mqttRoot); - return RootItemAndTwoTopics.of(model, mai.item, a, ab); + model.getRoot().setMqttRoot(mqttRoot); + return ModelItemAndTwoTopics.of(model, mai.item, a, ab); } - static class RootItemAndTwoTopics { - Root model; + static class ModelItemAndTwoTopics { + OpenHAB2Model model; NumberItem item; MqttTopic firstTopic; MqttTopic secondTopic; - static RootItemAndTwoTopics of(Root model, NumberItem item, MqttTopic firstTopic, MqttTopic secondTopic) { - RootItemAndTwoTopics result = new RootItemAndTwoTopics(); + static ModelItemAndTwoTopics of(OpenHAB2Model model, NumberItem item, MqttTopic firstTopic, MqttTopic secondTopic) { + ModelItemAndTwoTopics result = new ModelItemAndTwoTopics(); result.model = model; result.item = item; result.firstTopic = firstTopic; @@ -191,6 +197,13 @@ public class MqttTests { } } + private MqttTopic createAndAddMqttTopic(MqttRoot mqttRoot, String suffix) { + MqttTopic result = new MqttTopic(); + result.setTopicString(suffix); + mqttRoot.addTopic(result); + return result; + } + private <T> void assertTimeoutEquals(long seconds, T expected, Supplier<T> actualProvider) throws InterruptedException { if (expected == actualProvider.get()) { // already matched right now. return immediately. diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/NeuralNetworkTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/NeuralNetworkTest.java index 40295e580fcae466225c6717e2cad9fe48b248de..0eb593fd0fc6c3cb3401f315afe33d550e4b3658 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/NeuralNetworkTest.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/NeuralNetworkTest.java @@ -1,6 +1,8 @@ package de.tudresden.inf.st.eraser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.TestUtils; +import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; import org.junit.Assert; import org.junit.Test; @@ -13,7 +15,7 @@ public class NeuralNetworkTest { @Test public void simpleNetwork() { - TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(1); + ModelAndItem mai = TestUtils.createModelAndItem(1); /* I..Input, H..Hidden, O..Output * (I) -> (H) - 0.4 -> (O1) @@ -21,8 +23,8 @@ public class NeuralNetworkTest { * f= 4*x x>4? */ - NeuralNetworkRoot neuralNetworkRoot = new NeuralNetworkRoot(); - mai.model.getMachineLearningRoot().setPreferenceLearning(neuralNetworkRoot); + NeuralNetworkRoot neuralNetworkRoot = NeuralNetworkRoot.createEmpty(); + mai.model.getRoot().getMachineLearningRoot().setPreferenceLearning(neuralNetworkRoot); InputNeuron inputNeuron = new InputNeuron(); inputNeuron.setItem(mai.item); HiddenNeuron hiddenNeuron = new HiddenNeuron(); diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java index 18f51988bde6a5be42f35dd5e7d076a59069f983..8c109638fea74d074cfc72168303d68a5d2e69a7 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java @@ -1,5 +1,6 @@ package de.tudresden.inf.st.eraser; +import de.tudresden.inf.st.eraser.jastadd.model.OpenHAB2Model; import de.tudresden.inf.st.eraser.jastadd.model.Root; import de.tudresden.inf.st.eraser.jastadd_test.core.*; import de.tudresden.inf.st.eraser.openhab2.OpenHab2Importer; @@ -38,19 +39,24 @@ public class OpenHabImporterTest { protected URL makeURL(String formatUrlString, String hostAndPort) throws MalformedURLException { // read from files instead of making an http request switch (formatUrlString) { - case thingTypesUrl: return makeFileUrl("thing-types.json"); - case channelTypeUrl: return makeFileUrl("channel-types.json"); - case thingsUrl: return makeFileUrl("things.json"); - case itemsUrl: return makeFileUrl("items.json"); - case linksUrl: return makeFileUrl("links.json"); + case thingTypesUrl: return makeFileUrl("thing-types.json", "empty-list.json"); + case channelTypeUrl: return makeFileUrl("channel-types.json", "empty-list.json"); + case thingsUrl: return makeFileUrl("things.json", "empty-list.json"); + case itemsUrl: return makeFileUrl("items.json", "empty-list.json"); + case linksUrl: return makeFileUrl("links.json", "empty-list.json"); default: throw new IllegalArgumentException("Unsupported formatURLString: " + formatUrlString); } } - private URL makeFileUrl(String fileName) throws MalformedURLException { + @Override + protected URL makeURL(String formatUrlString, String hostAndPort, String id) throws MalformedURLException { + return makeFileUrl(id + ".json", "empty.json"); + } + + private URL makeFileUrl(String fileName, String emptyFileName) throws MalformedURLException { Path path = Paths.get(directory, fileName); if (!path.toFile().exists() || !path.toFile().isFile()) { - path = Paths.get(directory, "..", "empty.json"); + path = Paths.get(directory, "..", emptyFileName); } return path.toUri().toURL(); } @@ -65,7 +71,7 @@ public class OpenHabImporterTest { // call the modified importer, with the static host name, and an arbitrary chosen port // port will not be used during the test - Root model = importer.importFrom(HOST, 80); + OpenHAB2Model model = importer.importFrom(HOST, 80); if (model == null) { if (expected == Result.PARSE_FAILED) return; @@ -83,7 +89,7 @@ public class OpenHabImporterTest { } } - printAndCompare(config, model); + printAndCompare(config, model.getRoot()); } } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ParserTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ParserTests.java deleted file mode 100644 index 3ce0ecaf9e7f0469be91815ad4eb2ed57ed9ef2c..0000000000000000000000000000000000000000 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ParserTests.java +++ /dev/null @@ -1,104 +0,0 @@ -package de.tudresden.inf.st.eraser; - -import de.tudresden.inf.st.eraser.TestUtils.ModelAndItem; -import de.tudresden.inf.st.eraser.jastadd.model.MqttRoot; -import de.tudresden.inf.st.eraser.jastadd.model.NumberItem; -import de.tudresden.inf.st.eraser.jastadd.model.Root; -import de.tudresden.inf.st.eraser.util.ParserUtils; -import org.junit.Assert; -import org.junit.Test; - -/** - * Testing helper methods using in parsing. - * - * @author rschoene - Initial contribution - */ -public class ParserTests { - - @Test - public void testCreateMqttTopicSimple() { - ModelAndItem mai = TestUtils.createModelAndItem(1); - - Root model = mai.model; - model.setMqttRoot(new MqttRoot()); - NumberItem item = mai.item; - - Assert.assertNull(item.getTopic()); - - String[] parts = { "one", "two", "three" }; - String topicName = String.join("/", parts); - - ParserUtils.createMqttTopic(item, topicName, model); - - Assert.assertNotNull(item.getTopic()); - - MqttRoot mqttRoot = model.getMqttRoot(); - Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic()); - Assert.assertEquals("First part is wrong", - parts[0], mqttRoot.getTopic(0).getPart()); - Assert.assertEquals("First topic has wrong number of sub-topics", - 1, mqttRoot.getTopic(0).getNumSubTopic()); - Assert.assertEquals("Second part is wrong", - parts[1], mqttRoot.getTopic(0).getSubTopic(0).getPart()); - Assert.assertEquals("Second topic has wrong number of sub-topics", - 1, mqttRoot.getTopic(0).getSubTopic(0).getNumSubTopic()); - Assert.assertEquals("Third part is wrong", - parts[2], mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0).getPart()); - Assert.assertEquals("Third part is wrong object", - item.getTopic(), mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0)); - - Assert.assertEquals("Name does not match", topicName, item.getTopic().allParts()); - } - - - @Test - public void testCreateMqttTopicTwoInterleavedTopics() { - ModelAndItem mai = TestUtils.createModelAndItem(1); - - Root model = mai.model; - model.setMqttRoot(new MqttRoot()); - NumberItem item1 = mai.item; - NumberItem item2 = TestUtils.addItemTo(model, 3); - - Assert.assertNull(item1.getTopic()); - Assert.assertNull(item2.getTopic()); - - String[] parts = { "one", "two", "three" }; - String otherPart2 = "222"; - String topicName1 = String.join("/", parts); - String topicName2 = String.join("/", parts[0], otherPart2, parts[2]); - - ParserUtils.createMqttTopic(item1, topicName1, model); - ParserUtils.createMqttTopic(item2, topicName2, model); - - Assert.assertNotNull(item1.getTopic()); - Assert.assertNotNull(item2.getTopic()); - - MqttRoot mqttRoot = model.getMqttRoot(); - Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic()); - Assert.assertEquals("First part is wrong", - parts[0], mqttRoot.getTopic(0).getPart()); - Assert.assertEquals("First topic has wrong number of sub-topics", - 2, mqttRoot.getTopic(0).getNumSubTopic()); - Assert.assertEquals("Second part for first item is wrong", - parts[1], mqttRoot.getTopic(0).getSubTopic(0).getPart()); - Assert.assertEquals("Second part for first item is wrong", - otherPart2, mqttRoot.getTopic(0).getSubTopic(1).getPart()); - Assert.assertEquals("Second topic for first item has wrong number of sub-topics", - 1, mqttRoot.getTopic(0).getSubTopic(0).getNumSubTopic()); - Assert.assertEquals("Third part for first item is wrong", - parts[2], mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0).getPart()); - Assert.assertEquals("Second topic for second item has wrong number of sub-topics", - 1, mqttRoot.getTopic(0).getSubTopic(1).getNumSubTopic()); - Assert.assertEquals("Third part for second item is wrong", - parts[2], mqttRoot.getTopic(0).getSubTopic(1).getSubTopic(0).getPart()); - Assert.assertEquals("Third part for first item is wrong object", - item1.getTopic(), mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0)); - Assert.assertEquals("Third part for second item is wrong object", - item2.getTopic(), mqttRoot.getTopic(0).getSubTopic(1).getSubTopic(0)); - - Assert.assertEquals("Name for first item does not match", topicName1, item1.getTopic().allParts()); - Assert.assertEquals("Name for second item does not match", topicName2, item2.getTopic().allParts()); - } - -} 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 54b4d64e3498005d57a9c7eea53274764de77e94..13f5a493652ee42cdc70fc31ae64649de4b23987 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 @@ -1,12 +1,22 @@ package de.tudresden.inf.st.eraser; +import beaver.Parser; 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 org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.StreamSupport; /** * Testing the simple rule engine. @@ -16,202 +26,707 @@ import java.util.Arrays; @RunWith(Parameterized.class) public class RulesTest { - class Counter implements ActionEditConsumer { - int value = 0; + private static final double DELTA = 0.01d; + + class CountingAction extends NoopAction { + final Map<Item, AtomicInteger> counters = new HashMap<>(); + + CountingAction() { + reset(); + } + + private AtomicInteger getAtomic(Item item) { + return counters.computeIfAbsent(item, unused -> new AtomicInteger(0)); + } + @Override - public void accept(Root root) { - value += 1; + public void applyFor(Item item) { + getAtomic(item).addAndGet(1); + } + + int get(Item item) { + return getAtomic(item).get(); + } + + void reset() { + counters.clear(); } } @Test - public void testUnconditionalRule() { + public void testUnconditionalLambdaAction() { TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); - Root model = modelAndItem.model; + Root root = modelAndItem.model.getRoot(); NumberItem item = modelAndItem.item; + Rule rule = new Rule(); - Event event = new Event("testEvent"); - Counter counter = new Counter(); - rule.addEvent(event); - rule.setCondition(root -> true); - rule.setAction(counter); - model.addRule(rule); - event.activateFor(item); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); - Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.value); + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); - setState(model, item, 5); - Assert.assertEquals(m("Change of item state should trigger the event"), 1, counter.value); + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); - setState(model, item, 5); - Assert.assertEquals(m("Set state to same value should not trigger the event"), 1, counter.value); + setState(item, 5); + Assert.assertEquals(m("Set state to same value should not trigger the rule"), 1, counter.get(item)); - setState(model, item, 3); - Assert.assertEquals(m("Change of item state should trigger the event"), 2, counter.value); + setState(item, 3); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); } @Test - public void testOneRuleTwoEventsForOneItem() { + public void testIdempotentActivation() { TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); - Root model = modelAndItem.model; + Root root = modelAndItem.model.getRoot(); NumberItem item = modelAndItem.item; Rule rule = new Rule(); - Counter counter = new Counter(); - rule.setCondition(root -> true); - rule.setAction(counter); - model.addRule(rule); + root.addRule(rule); - Event event1 = new Event("testEvent1"); - rule.addEvent(event1); + rule.activateFor(item); + rule.activateFor(item); - Event event2 = new Event("testEvent2"); - rule.addEvent(event2); + Assert.assertEquals(m("Rule was not activated exactly once (observer count)"), 1, rule.getObserverList().size()); + int ruleCount = 0; + for (ItemObserver observer : rule.getObserverList()) { + ruleCount += observer.getTriggeredRuleList().size(); + } + Assert.assertEquals(m("Rule was not activated exactly once (rule count)"), 1, ruleCount); + } + + @Test + public void testTwoRulesForOneItem() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; - event1.activateFor(item); - event2.activateFor(item); + CountingAction counter1 = new CountingAction(); + Rule ruleA = new Rule(); + ruleA.addAction(counter1); + root.addRule(ruleA); - Assert.assertEquals(m("First counter not initialized correctly"), 0, counter.value); + Rule ruleB = new Rule(); + CountingAction counter2 = new CountingAction(); + ruleB.addAction(counter2); + root.addRule(ruleB); - setState(model, item, 5); - Assert.assertEquals(m("Change of item state should trigger the rule twice"), 2, counter.value); + ruleA.activateFor(item); + ruleB.activateFor(item); - setState(model, item, 5); - Assert.assertEquals(m("Set state to same value should not trigger the rule"), 2, counter.value); + Assert.assertEquals(m("First counter not initialized correctly"), 0, counter1.get(item)); + Assert.assertEquals(m("Second counter not initialized correctly"), 0, counter2.get(item)); - setState(model, item, 3); - Assert.assertEquals(m("Change of item state should trigger the rule again twice"), 4, counter.value); + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the first rule"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of item state should trigger the second rule"), 1, counter2.get(item)); + + setState(item, 5); + Assert.assertEquals(m("Set state to same value should not trigger the first rule"), 1, counter1.get(item)); + Assert.assertEquals(m("Set state to same value should not trigger the second rule"), 1, counter2.get(item)); + + setState(item, 3); + Assert.assertEquals(m("Change of item state should trigger the first rule"), 2, counter1.get(item)); + Assert.assertEquals(m("Change of item state should trigger the second rule"), 2, counter2.get(item)); } @Test - public void testTwoRulesForOneItem() { + public void testOneRuleForTwoItems() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); + Root root = modelAndItem.model.getRoot(); + NumberItem item1 = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); + + Rule rule = new Rule(); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + + rule.activateFor(item1); + rule.activateFor(item2); + + // Items: 4, 4. Expected counters: 0, 0. + Assert.assertEquals(m("Counter not initialized correctly for first item"), 0, counter.get(item1)); + Assert.assertEquals(m("Counter not initialized correctly for second item"), 0, counter.get(item2)); + + // Items: 5, 4. Expected counters: 1, 0. + setState(item1, 5); + Assert.assertEquals(m("Change of first item state should trigger the rule once"), 1, counter.get(item1)); + Assert.assertEquals(m("Change of first item state should not trigger the rule for second item"), 0, counter.get(item2)); + + // Items: 5, 4. Expected counters: 1, 0. + setState(item1, 5); + Assert.assertEquals(m("Set state of first item to same value should not trigger the rule"), + 1, counter.get(item1)); + Assert.assertEquals(m("Change of first item state should not trigger the rule for second item"), 0, counter.get(item2)); + + // Items: 5, 3. Expected counters: 1, 1. + setState(item2, 3); + Assert.assertEquals(m("Change of second item state should trigger the rule"), 1, counter.get(item2)); + Assert.assertEquals(m("Change of second item state should not trigger the rule for first item"), 1, counter.get(item1)); + + // Items: 5, 3. Expected counters: 1, 1. + setState(item1, 5); + Assert.assertEquals(m("Set state of first item to same value should still not trigger the rule"), + 1, counter.get(item1)); + Assert.assertEquals(m("Change of first item state should still not trigger the rule for second item"), 1, counter.get(item2)); + + // Items: 6, 3. Expected counters: 2, 1. + setState(item1, 6); + Assert.assertEquals(m("Set state of first item should trigger the rule"), + 2, counter.get(item1)); + Assert.assertEquals(m("Change of first item state should still not trigger the rule for second item"), 1, counter.get(item2)); + + // Items: 6, 3. Expected counters: 2, 1. + setState(item2, 3); + Assert.assertEquals(m("Set state of second item to same value should not trigger the rule"), + 1, counter.get(item2)); + Assert.assertEquals(m("Change of second item state should not trigger the rule for first item"), 2, counter.get(item1)); + + // Items: 6, 0. Expected counters: 2, 2. + setState(item2, 0); + Assert.assertEquals(m("Change of second item state should trigger the rule"), 2, counter.get(item2)); + Assert.assertEquals(m("Change of second item state should not trigger the rule for first item"), 2, counter.get(item1)); + } + + @Test + public void testRemoveActivation() { TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); - Root model = modelAndItem.model; + Root root = modelAndItem.model.getRoot(); NumberItem item = modelAndItem.item; - Counter counter1 = new Counter(); - Rule rule1 = new Rule(); - rule1.setCondition(root -> true); - rule1.setAction(counter1); - model.addRule(rule1); + Rule rule = new Rule(); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + + rule.activateFor(item); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); - Event event1 = new Event("testEvent1"); - rule1.addEvent(event1); + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); - Rule rule2 = new Rule(); - Counter counter2 = new Counter(); - rule2.setCondition(root -> true); - rule2.setAction(counter2); - model.addRule(rule2); + rule.removeActivationOf(item); - Event event2 = new Event("testEvent2"); - rule2.addEvent(event2); + setState(item, 3); + Assert.assertEquals(m("Change of item state should not change the counter anymore"), 1, counter.get(item)); + } - event1.activateFor(item); - event2.activateFor(item); + @Test + public void testNumberConditions() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(2); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; - Assert.assertEquals(m("First counter not initialized correctly"), 0, counter1.value); - Assert.assertEquals(m("Second counter not initialized correctly"), 0, counter2.value); + 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)); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); - setState(model, item, 5); - Assert.assertEquals(m("Change of item state should trigger the first event"), 1, counter1.value); - Assert.assertEquals(m("Change of item state should trigger the second event"), 1, counter2.value); + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); - setState(model, item, 5); - Assert.assertEquals(m("Set state to same value should not trigger the first event"), 1, counter1.value); - Assert.assertEquals(m("Set state to same value should not trigger the second event"), 1, counter2.value); + setState(item, 3); + Assert.assertEquals(m("Change of item to 3 should not trigger the rule, check1 violated"), 0, counter.get(item)); - setState(model, item, 3); - Assert.assertEquals(m("Change of item state should trigger the first event"), 2, counter1.value); - Assert.assertEquals(m("Change of item state should trigger the second event"), 2, counter2.value); + setState(item, 4); + Assert.assertEquals(m("Change of item to 4 should trigger the rule"), 1, counter.get(item)); + + setState(item, 5); + Assert.assertEquals(m("Change of item to 5 should trigger the rule"), 2, counter.get(item)); + + setState(item, 6); + Assert.assertEquals(m("Change of item to 6 should not trigger the rule, check2 violated"), 2, counter.get(item)); + + setState(item, 7); + Assert.assertEquals(m("Change of item to 7 should not trigger the rule, check2 violated"), 2, counter.get(item)); } @Test - public void testOneRuleOneEventForTwoItems() { - TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); - Root model = modelAndItem.model; - NumberItem item1 = modelAndItem.item; - NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem); + public void testTwoActions() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(2); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; Rule rule = new Rule(); - Counter counter = new Counter(); - rule.setCondition(root -> true); - rule.setAction(counter); - model.addRule(rule); + CountingAction counter1 = new CountingAction(); + rule.addAction(counter1); + CountingAction counter2 = new CountingAction(); + rule.addAction(counter2); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("First counter not initialized correctly"), 0, counter1.get(item)); + Assert.assertEquals(m("Second counter not initialized correctly"), 0, counter2.get(item)); + + setState(item, 3); + Assert.assertEquals(m("Change of item state should trigger the rule for first counter"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of item state should trigger the rule for second counter"), 1, counter2.get(item)); + + setState(item, 3); + Assert.assertEquals(m("Change of item to same state should not trigger the rule for first counter"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of item to same state should not trigger the rule for second counter"), 1, counter2.get(item)); + } - Event eventItem1 = new Event("testEventItem1"); - rule.addEvent(eventItem1); - eventItem1.activateFor(item1); + @Test + public void testChainedRules() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(2); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); + + Rule ruleA = new Rule(); + CountingAction counter1 = new CountingAction(); + ruleA.addAction(counter1); + + Rule ruleB = new Rule(); + CountingAction counter2 = new CountingAction(); + ruleB.addAction(counter2); + + ruleA.addAction(new TriggerRuleAction(ruleB)); + + root.addRule(ruleA); + root.addRule(ruleB); + ruleA.activateFor(item); + ruleB.activateFor(item2); + + Assert.assertEquals(m("First counter not initialized correctly for first item"), 0, counter1.get(item)); + Assert.assertEquals(m("First counter not initialized correctly for second item"), 0, counter1.get(item2)); + Assert.assertEquals(m("Second counter not initialized correctly for first item"), 0, counter2.get(item)); + Assert.assertEquals(m("Second counter not initialized correctly for second item"), 0, counter2.get(item2)); + + setState(item, 3); + Assert.assertEquals(m("Change of first item state should trigger the first rule"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of first item state should trigger the second rule"), 1, counter2.get(item)); + Assert.assertEquals(m("Change of first item state should not trigger the second rule for second item"), 0, counter2.get(item2)); + + setState(item, 3); + Assert.assertEquals(m("Change of item to same state should not trigger the first rule"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of item to same state should not trigger the second rule"), 1, counter2.get(item)); + Assert.assertEquals(m("Change of first item state should not trigger the second rule for second item"), 0, counter2.get(item2)); + + setState(item2, 7); + Assert.assertEquals(m("Change of second item state should not trigger the first rule"), 1, counter1.get(item)); + Assert.assertEquals(m("Change of second item state should not trigger the second rule for the first item"), 1, counter2.get(item)); + Assert.assertEquals(m("Change of second item state should trigger the second rule for the second item"), 1, counter2.get(item2)); + } - Event eventItem2 = new Event("testEventItem2"); - rule.addEvent(eventItem2); - eventItem2.activateFor(item2); + @Test + public void testSetStateFromConstantStringAction() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); - Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.value); + Rule rule = new Rule(); + rule.addAction(new SetStateFromConstantStringAction(item2, "5")); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("Affected item not initialized correctly"), + 4, item2.asItemWithDoubleState().getState(), DELTA); + + setState(item, 25); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 5, item2.asItemWithDoubleState().getState(), DELTA); + } - setState(model, item1, 5); - Assert.assertEquals(m("Change of first item state should trigger the event once"), 1, counter.value); + class ValuedStateProvider implements NewStateProvider { + int value; + @Override + public String get() { + return Integer.toString(value); + } + } - setState(model, item1, 5); - Assert.assertEquals(m("Set state of first item to same value should not trigger the event"), - 1, counter.value); + @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.getOpenHAB2Model(), 3, useUpdatingItem); - setState(model, item2, 3); - Assert.assertEquals(m("Change of second item state should trigger the event"), 2, counter.value); + Rule rule = new Rule(); + rule.addAction(new SetStateFromLambdaAction(item2, provider)); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("Affected item not initialized correctly"), + 3, item2.asItemWithDoubleState().getState(), DELTA); + + provider.value = 4; + setState(item, 1); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 4, item2.asItemWithDoubleState().getState(), DELTA); + + provider.value = 4; + setState(item, 2); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 4, item2.asItemWithDoubleState().getState(), DELTA); + + provider.value = 5; + setState(item, 2); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + 4, item2.asItemWithDoubleState().getState(), DELTA); + + provider.value = 5; + setState(item, 3); + Assert.assertEquals(m("Change of item state should trigger the rule"), 3, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 5, item2.asItemWithDoubleState().getState(), DELTA); + } - setState(model, item1, 5); - Assert.assertEquals(m("Set state of first item to same value should still not trigger the event"), - 2, counter.value); + @Test + public void testSetStateFromTriggeringItemAction() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; + StringItem item2 = addStringItem(root.getOpenHAB2Model(), "0"); - setState(model, item1, 6); - Assert.assertEquals(m("Set state of first item should trigger the event"), - 3, counter.value); + Rule rule = new Rule(); + CountingAction counter = new CountingAction(); + rule.addAction(new SetStateFromTriggeringItemAction(item2)); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); + Assert.assertEquals(m("Affected item not initialized correctly"), "0", item2.getState()); + + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), "5.0", item2.getState()); + + setState(item, 5); + Assert.assertEquals(m("Change of item state should not trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should not set the state of the affected item"), "5.0", item2.getState()); + + setState(item, 7); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), "7.0", item2.getState()); + } - setState(model, item2, 3); - Assert.assertEquals(m("Set state of second item to same value should not trigger the event"), - 3, counter.value); + @Test + public void testSetStateFromItemsAction() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); + StringItem affectedItem = addStringItem(root.getOpenHAB2Model(), "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); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); + Assert.assertEquals(m("Affected item not initialized correctly"), "1", + affectedItem.getState()); + + // 5 + 4 = 9 + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + "9", affectedItem.getState()); + + // still 5 + 4 = 9, as rule does not trigger for item2 + setState(item2, 5); + Assert.assertEquals(m("Change of item2 state should not trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item2 state should not set the state of the affected item"), + "9", affectedItem.getState()); + + // still 5 + 4 = 9, as rule should not trigger + setState(item, 5); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + "9", affectedItem.getState()); + + // 7 + 5 = 12 + setState(item, 7); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + "12", affectedItem.getState()); + + // add new item to sum + NumberItem item3 = TestUtils.addItemTo(root.getOpenHAB2Model(), -4, useUpdatingItem); + action.addSourceItem(item3); + + // still 7 + 5 = 12, as rule should not trigger + setState(item, 7); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + "12", affectedItem.getState()); + + // 8 + 5 - 4 = 9 + setState(item, 8); + Assert.assertEquals(m("Change of item state should trigger the rule"), 3, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + "9", affectedItem.getState()); } @Test - public void testEventRemoveSelf() { - TestUtils.ModelAndItem modelAndItem = createModelAndItem(4); - Root model = modelAndItem.model; + public void testAddDoubleToStateAction() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); NumberItem item = modelAndItem.item; + NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); Rule rule = new Rule(); - Counter counter = new Counter(); - rule.setCondition(root -> true); - rule.setAction(counter); - model.addRule(rule); + rule.addAction(new AddDoubleToStateAction(affectedItem, 2)); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); + Assert.assertEquals(m("Affected item not initialized correctly"), + 4, affectedItem.getState(), DELTA); + + // 4 + 2 = 6 + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 6, affectedItem.getState(), DELTA); + + // still 6 + setState(item, 5); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + 6, affectedItem.getState(), DELTA); + + // 6 + 2 = 8 + setState(item, -2); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 8, affectedItem.getState(), DELTA); + } - Event event = new Event("testEvent1"); - rule.addEvent(event); + @Test + public void testMultiplyDoubleToStateAction() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item = modelAndItem.item; + NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); - event.activateFor(item); + Rule rule = new Rule(); + rule.addAction(new MultiplyDoubleToStateAction(affectedItem, 2)); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + root.addRule(rule); + rule.activateFor(item); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item)); + Assert.assertEquals(m("Affected item not initialized correctly"), + 4, affectedItem.getState(), DELTA); + + // 4 * 2 = 8 + setState(item, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 8, affectedItem.getState(), DELTA); + + // still 8 + setState(item, 5); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 1, counter.get(item)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + 8, affectedItem.getState(), DELTA); + + // 8 * 2 = 16 + setState(item, 0); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 16, affectedItem.getState(), DELTA); + } + + @Test + public void testChainAddMultiplyActions() { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item1 = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); + NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 5, useUpdatingItem); + + Rule ruleA = new Rule(); + ruleA.addAction(new AddDoubleToStateAction(affectedItem, 2)); + CountingAction counterA = new CountingAction(); + ruleA.addAction(counterA); + + Rule ruleB = new Rule(); + ruleB.addAction(new MultiplyDoubleToStateAction(affectedItem, 3)); + CountingAction counterB = new CountingAction(); + ruleB.addAction(counterB); + + ruleA.addAction(new TriggerRuleAction(ruleB)); + + root.addRule(ruleA); + root.addRule(ruleB); + ruleA.activateFor(item1); + ruleB.activateFor(item2); + + Assert.assertEquals(m("CounterA not initialized correctly for first item"), 0, counterA.get(item1)); + Assert.assertEquals(m("CounterA not initialized correctly for second item"), 0, counterA.get(item2)); + Assert.assertEquals(m("CounterB not initialized correctly for first item"), 0, counterB.get(item1)); + Assert.assertEquals(m("CounterB not initialized correctly for second item"), 0, counterB.get(item2)); + Assert.assertEquals(m("Affected item not initialized correctly"), + 5, affectedItem.getState(), DELTA); + + // First, 5 + 2 = 7. Then, 7 * 3 = 21 + setState(item1, 5); + Assert.assertEquals(m("Change of item state should trigger the ruleA"), 1, counterA.get(item1)); + Assert.assertEquals(m("Change of item state should also trigger the ruleB"), 1, counterB.get(item1)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 21, affectedItem.getState(), DELTA); + + // still 21 + setState(item1, 5); + Assert.assertEquals(m("Change of item to same state should not trigger the ruleA"), 1, counterA.get(item1)); + Assert.assertEquals(m("Change of item to same state should not trigger the ruleB"), 1, counterB.get(item1)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + 21, affectedItem.getState(), DELTA); + + // Only, 21 * 3 = 63 + setState(item2, 1); + Assert.assertEquals(m("Change of second item state should not trigger the ruleA"), 1, counterA.get(item1)); + Assert.assertEquals(m("Change of second item state should not trigger the ruleB for first item"), 1, counterB.get(item1)); + Assert.assertEquals(m("Change of second item state should trigger the ruleB for second item"), 1, counterB.get(item2)); + Assert.assertEquals(m("Change of second item state should set the state of the affected item"), + 63, affectedItem.getState(), DELTA); + } - Assert.assertEquals(m("First counter not initialized correctly"), 0, counter.value); + @Test + public void testSetFromExpression() throws IOException, Parser.Exception { + TestUtils.ModelAndItem modelAndItem = createModelAndItem(3); + Root root = modelAndItem.model.getRoot(); + NumberItem item1 = modelAndItem.item; + NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem); + NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 5, useUpdatingItem); - setState(model, item, 5); - Assert.assertEquals(m("Change of item state should trigger the event"), 1, counter.value); + Rule rule = new Rule(); + SetStateFromExpression action = new SetStateFromExpression(); + // TODO item1 should be referred to as triggering item + action.setNumberExpression(ParserUtils.parseNumberExpression("(" + item1.getID() + " + " + item2.getID() + ")", root)); + action.setAffectedItem(affectedItem); + rule.addAction(action); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + + root.addRule(rule); + rule.activateFor(item1); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item1)); + Assert.assertEquals(m("Second item not initialized correctly"), + 4, item2.getState(), DELTA); + Assert.assertEquals(m("Affected item not initialized correctly"), + 5, affectedItem.getState(), DELTA); + + // 5 + 4 = 9 + setState(item1, 5); + Assert.assertEquals(m("Change of item state should trigger the rule"), 1, counter.get(item1)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 9, affectedItem.getState(), DELTA); + + // still 9 + setState(item1, 5); + Assert.assertEquals(m("Change of item to same state should not trigger the rule"), 1, counter.get(item1)); + Assert.assertEquals(m("Change of item to same state should not set the state of the affected item"), + 9, affectedItem.getState(), DELTA); + + // still 9 (changes of item2 do not trigger the rule) + setState(item2, 1); + Assert.assertEquals(m("Change of second item to same state should not trigger the rule"), 1, counter.get(item1)); + Assert.assertEquals(m("Change of second item to same state should not set the state of the affected item"), + 9, affectedItem.getState(), DELTA); + + // 0 + 1 = 1 + setState(item1, 0); + Assert.assertEquals(m("Change of item state should trigger the rule"), 2, counter.get(item1)); + Assert.assertEquals(m("Change of item state should set the state of the affected item"), + 1, affectedItem.getState(), DELTA); + } - event.removeSelf(); + @Test + public void testCronJobRule() { + Rule rule = new Rule(); + CountingAction counter = new CountingAction(); + rule.addAction(counter); + + Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(null)); + + ScheduledFuture f = rule.activateEvery(50, TimeUnit.MILLISECONDS); + waitMillis(160); + Assert.assertTrue("Rule cron job could not be cancelled", f.cancel(true)); + // ----------------------- 160ms ----------------------- + // + -- 50ms -- + -- 50ms -- + -- 50 ms -- + -- 10 ms -- + // 4 times executed (+), with no initial delay + Assert.assertEquals(m("Rule was not executed four times"), 4, counter.get(null)); + + counter.reset(); + Assert.assertEquals(m("Counter not reset correctly"), 0, counter.get(null)); + + f = rule.activateEvery(80, 50, TimeUnit.MILLISECONDS); + waitMillis(150); + Assert.assertTrue("Rule cron job could not be cancelled", f.cancel(true)); + // ------------------ 150ms ------------------ + // ----- 80ms ----- + -- 50ms -- + -- 20 ms -- + // 2 times executed (+), with given delay + Assert.assertEquals(m("Rule was not executed two times"), 2, counter.get(null)); + } - setState(model, item, 3); - Assert.assertEquals(m("Change of item state should not change the counter anymore"), 1, counter.value); + private static void waitMillis(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + Assert.fail("Sleeping was interrupted!"); + } } private String m(String message) { return message + " (Using " + name + ")"; } + private StringItem addStringItem(OpenHAB2Model model, String initialValue) { + StringItem item = new StringItem(); + Group group = TestUtils.getDefaultGroup(model); + item.setID("item" + group.getNumItem()); + item.setState(initialValue, false); + group.addItem(item); + return item; + } + private TestUtils.ModelAndItem createModelAndItem(long initialValue) { return TestUtils.createModelAndItem(initialValue, useUpdatingItem); } - private void setState(Root model, NumberItem item, long newState) { + private void setState(NumberItem item, long newState) { item.setState(newState); - if (!useUpdatingItem) { - model.applyRules(); + if (!useUpdatingItem && item.hasItemObserver()) { + item.getItemObserver().apply(); } } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/TestUtils.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/TestUtils.java deleted file mode 100644 index a65b78cb62f2c8c8d27c4d2cc5054c572faa677c..0000000000000000000000000000000000000000 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/TestUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.tudresden.inf.st.eraser; - -import de.tudresden.inf.st.eraser.jastadd.model.*; - -/** - * Helper class to create models used in tests. - * - * @author rschoene - Initial contribution - */ -class TestUtils { - - static class ModelAndItem { - Root model; - NumberItem item; - static ModelAndItem of(Root model, NumberItem item) { - ModelAndItem result = new ModelAndItem(); - result.model = model; - result.item = item; - return result; - } - } - - static ModelAndItem createModelAndItem(double initialValue) { - return createModelAndItem(initialValue, false); - } - - static ModelAndItem createModelAndItem(double initialValue, boolean useUpdatingItem) { - Root model = Root.createEmptyRoot(); - Group g = new Group(); - model.addGroup(g); - g.setID("group1"); - - NumberItem item = addItemTo(g, initialValue, useUpdatingItem); - - return ModelAndItem.of(model, item); - } - - static NumberItem addItemTo(Root model, double initialValue) { - return addItemTo(model, initialValue, false); - } - - static NumberItem addItemTo(Group group, double initialValue) { - return addItemTo(group, initialValue, false); - } - - static NumberItem addItemTo(Root model, double initialValue, boolean useUpdatingItem) { - // use first found group - Group group = model.getGroup(0); - return addItemTo(group, initialValue, useUpdatingItem); - } - - private static NumberItem addItemTo(Group group, double initialValue, boolean useUpdatingItem) { - NumberItem item = new NumberItem(); - item.setDefaultShouldSendState(useUpdatingItem); - group.addItem(item); - item.setID("item" + group.getNumItem()); - item.setState(initialValue, false); - return item; - } - - -} diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestRunner.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestRunner.java index 984fb21d95d17d02b3ef7c07d064c410e300afb2..6978df5cafeee586a7025fc1855d88b1b240a49b 100644 --- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestRunner.java +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestRunner.java @@ -30,6 +30,7 @@ package de.tudresden.inf.st.eraser.jastadd_test.core; import beaver.Parser; +import de.tudresden.inf.st.eraser.jastadd.model.OpenHAB2Model; import de.tudresden.inf.st.eraser.jastadd.model.Root; import de.tudresden.inf.st.eraser.util.ParserUtils; @@ -67,9 +68,9 @@ public class TestRunner { Result expected = config.expected; // Parse input model - Root model; + Root root; try { - model = parseModel(config); + root = parseModel(config); } catch (Parser.Exception e) { if (expected == Result.PARSE_FAILED) return; // otherwise rethrow error @@ -80,7 +81,7 @@ public class TestRunner { fail("Parsing the model should have failed, but was successful!"); } - if (model == null) { + if (root == null) { fail("Parsing the model should have passed, but model was null!"); } @@ -91,15 +92,15 @@ public class TestRunner { return; } - printAndCompare(config, model); + printAndCompare(config, root); } - protected static void printAndCompare(TestConfiguration config, Root model) { + protected static void printAndCompare(TestConfiguration config, Root root) { Result expected = config.expected; // Print model. String output; try { - output = printModel(model, config); + output = printModel(root, config); } catch (Exception e) { if (expected == Result.PRINT_FAILED) return; // otherwise rethrow error @@ -149,8 +150,8 @@ public class TestRunner { return new File(testDir, "jastadd.err.expected"); } - private static String printModel(Root model, TestConfiguration config) { - return model.prettyPrint(); + private static String printModel(Root root, TestConfiguration config) { + return root.prettyPrint(); } /** diff --git a/eraser-base/src/test/resources/log4j2-test.xml b/eraser-base/src/test/resources/log4j2-test.xml index 5c534092d64e9c1834c2ba20208c057e2b56be16..b20ed4ecc82af01ce5278a8d9f0c1aa77a32f00b 100644 --- a/eraser-base/src/test/resources/log4j2-test.xml +++ b/eraser-base/src/test/resources/log4j2-test.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser-test.log" + filePattern="logs/eraser-test-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> @@ -21,5 +21,14 @@ <Logger name="de.tudresden.inf.st.eraser.openhab2.mqtt" level="DEBUG" additivity="false"> <Appender-ref ref="Console"/> </Logger> + <!-- Testcontainers reduce noise--> + <Logger name="org.testcontainers" level="INFO" additivity="false"> + <Appender-ref ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Logger> + <Logger name="com.github.dockerjava.core" level="INFO" additivity="false"> + <Appender-ref ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Logger> </Loggers> </Configuration> diff --git a/eraser-base/src/test/resources/openhabtest/empty-list.json b/eraser-base/src/test/resources/openhabtest/empty-list.json new file mode 100644 index 0000000000000000000000000000000000000000..fe51488c7066f6687ef680d6bfaa4f7768ef205c --- /dev/null +++ b/eraser-base/src/test/resources/openhabtest/empty-list.json @@ -0,0 +1 @@ +[] diff --git a/eraser-base/src/test/resources/openhabtest/empty.json b/eraser-base/src/test/resources/openhabtest/empty.json index fe51488c7066f6687ef680d6bfaa4f7768ef205c..0967ef424bce6791893e9a57bb952f80fd536e93 100644 --- a/eraser-base/src/test/resources/openhabtest/empty.json +++ b/eraser-base/src/test/resources/openhabtest/empty.json @@ -1 +1 @@ -[] +{} diff --git a/eraser-base/src/test/resources/openhabtest/oh1/output.eraser b/eraser-base/src/test/resources/openhabtest/oh1/output.eraser index 47453bd59614c3e358bc7996a4e2c4615bfffaf5..ac1a7993a8817a10c197fb73dd844bc2d750753f 100644 --- a/eraser-base/src/test/resources/openhabtest/oh1/output.eraser +++ b/eraser-base/src/test/resources/openhabtest/oh1/output.eraser @@ -1,5 +1,5 @@ Color Item: id="Go1_item" label="Go 1" state="0,0,0" category="Lighting" ; -Switch Item: id="Rule_Switch" state="false" ; +Switch Item: id="Rule_Switch" state="OFF" ; Number Item: id="Color_Manual_Slider" state="0.0" ; Number Item: id="watch_acceleration_x" label="Watch Acceleration X" state="0.0" ; Number Item: id="watch_acceleration_y" label="Watch Acceleration Y" state="0.0" ; @@ -16,7 +16,5 @@ Number Item: id="polar_brightness" label="Polar Brightness" state="0.0" category Number Item: id="moto_360_brightness" label="Moto 360 Brightness" state="0.0" category="Brightness" ; Color Item: id="wohnzimmer_item" label="Wohnzimmer" state="0,0,0" category="Lighting" ; Color Item: id="iris1_item" label="Iris 1" state="226,100,98" category="Lighting" ; -Group: id="all_dimmable_lamps" label="All dimmable lamps" aggregation="AVG" items=["Go1_item"] ; +Group: id="all_dimmable_lamps" label="All dimmable lamps" items=["Go1_item"] aggregation="AVG" ; Group: id="Unknown" items=["Rule_Switch", "Color_Manual_Slider", "watch_acceleration_x", "watch_acceleration_y", "watch_acceleration_z", "watch_rotation_x", "watch_rotation_y", "watch_rotation_z", "phone_rotation_x", "phone_rotation_y", "phone_rotation_z", "samsung_brightness", "skywriter_flick_item", "polar_brightness", "moto_360_brightness", "wohnzimmer_item", "iris1_item"] ; -Mqtt: incoming="" outgoing="" host="localhost" ; -Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/openhabtest/oh2/items.json b/eraser-base/src/test/resources/openhabtest/oh2/items.json index f57d140d070ef2caf360861b277a673d61a7b4e8..a26dfd88f1e76aeec2b34bd5e384c6b7ca59bd91 100644 --- a/eraser-base/src/test/resources/openhabtest/oh2/items.json +++ b/eraser-base/src/test/resources/openhabtest/oh2/items.json @@ -38,6 +38,15 @@ }, { "link": "http://localhost:48080/rest/items/iris1_item", + "metadata": { + "fuenf": { + "value": "value1", + "config": { + "key2": "value2", + "key3": "value3" + } + } + }, "state": "253,0,0", "editable": true, "type": "Color", diff --git a/eraser-base/src/test/resources/openhabtest/oh2/links.json b/eraser-base/src/test/resources/openhabtest/oh2/links.json index 79e79442806b87513d8581eb877453873069ddbe..f881db063fd53906ab14e3fda79dbf4a5fac0d4b 100644 --- a/eraser-base/src/test/resources/openhabtest/oh2/links.json +++ b/eraser-base/src/test/resources/openhabtest/oh2/links.json @@ -142,7 +142,7 @@ { "channelUID": "openlicht:polar-m600:342dfc32:rotation-y", "configuration": {}, - "itemName": "watch_acceleration_y" + "itemName": "watch_rotation_y" }, { "channelUID": "openlicht:polar-m600:342dfc32:rotation-z", diff --git a/eraser-base/src/test/resources/openhabtest/oh2/output.eraser b/eraser-base/src/test/resources/openhabtest/oh2/output.eraser index 155ab239c81206be07a20e0c861e3522aadd92ba..391d857ff48d13a04439dc4f5f16ba8cfc83b5a6 100644 --- a/eraser-base/src/test/resources/openhabtest/oh2/output.eraser +++ b/eraser-base/src/test/resources/openhabtest/oh2/output.eraser @@ -4,15 +4,15 @@ Thing: id="hue:0210:0017880adcf4:1" label="Wohnzimmer" type="hue:0210" channels= Thing: id="hue:0210:0017880adcf4:4" label="Hue go 1" type="hue:0210" channels=["hue:0210:0017880adcf4:4:color", "hue:0210:0017880adcf4:4:color_temperature", "hue:0210:0017880adcf4:4:alert", "hue:0210:0017880adcf4:4:effect"] ; Thing: id="openlicht:polar-m600:342dfc32" label="Polar M600" type="openlicht:polar-m600" channels=["openlicht:polar-m600:342dfc32:acceleration-x", "openlicht:polar-m600:342dfc32:acceleration-y", "openlicht:polar-m600:342dfc32:acceleration-z", "openlicht:polar-m600:342dfc32:rotation-x", "openlicht:polar-m600:342dfc32:rotation-y", "openlicht:polar-m600:342dfc32:rotation-z", "openlicht:polar-m600:342dfc32:activity", "openlicht:polar-m600:342dfc32:heart-rate", "openlicht:polar-m600:342dfc32:steps", "openlicht:polar-m600:342dfc32:brightness"] ; Thing: id="openlicht:samsung-s6:2ca84896" label="Samsung S6" type="openlicht:samsung-s6" channels=["openlicht:samsung-s6:2ca84896:brightness", "openlicht:samsung-s6:2ca84896:rotation-x", "openlicht:samsung-s6:2ca84896:rotation-y", "openlicht:samsung-s6:2ca84896:rotation-z"] ; -Thing: id="hue:bridge:0017880adcf4" label="Philips hue (10.8.0.160)" type="hue:bridge" channels=[] ; +Thing: id="hue:bridge:0017880adcf4" label="Philips hue (10.8.0.160)" type="hue:bridge" ; Color Item: id="Go1_item" label="Go 1" state="213,36,40" category="Lighting" ; String Item: id="skywriter_flick_item" label="Skywriter Flick Detected" state="" category="Motion" ; Color Item: id="wohnzimmer_item" label="Wohnzimmer" state="0,0,0" category="Lighting" ; -Color Item: id="iris1_item" label="Iris 1" state="253,0,0" category="Lighting" ; +Color Item: id="iris1_item" label="Iris 1" state="253,0,0" category="Lighting" metaData={"key2":"value2", "key3":"value3"} ; Number Item: id="moto_360_brightness" label="Moto 360 Brightness" state="0.0" category="Brightness" ; Number Item: id="polar_brightness" label="Polar Brightness" state="0.0" category="Brightness" ; String Item: id="samsung_brightness" label="Samsung Brightness" state="" category="Brightness" ; -Switch Item: id="Rule_Switch" state="false" ; +Switch Item: id="Rule_Switch" state="OFF" ; Number Item: id="Color_Manual_Slider" state="0.0" ; Number Item: id="watch_acceleration_x" label="Watch Acceleration X" state="6.264883611883931E-10" ; Number Item: id="watch_acceleration_y" label="Watch Acceleration Y" state="1.5765462769316607E-19" ; @@ -23,21 +23,21 @@ Number Item: id="watch_rotation_z" label="Watch Rotation Z" state="0.0" ; Number Item: id="phone_rotation_x" label="Phone Rotation X" state="0.0" ; Number Item: id="phone_rotation_y" label="Phone Rotation Y" state="0.0" ; Number Item: id="phone_rotation_z" label="Phone Rotation Z" state="0.0" ; -Group: id="all_dimmable_lamps" label="All dimmable lamps" aggregation="AVG" items=["Go1_item"] ; +Group: id="all_dimmable_lamps" label="All dimmable lamps" items=["Go1_item"] aggregation="AVG" ; Group: id="Unknown" items=["skywriter_flick_item", "wohnzimmer_item", "iris1_item", "moto_360_brightness", "polar_brightness", "samsung_brightness", "Rule_Switch", "Color_Manual_Slider", "watch_acceleration_x", "watch_acceleration_y", "watch_acceleration_z", "watch_rotation_x", "watch_rotation_y", "watch_rotation_z", "phone_rotation_x", "phone_rotation_y", "phone_rotation_z"] ; -ThingType: id="hue:0110" label="Dimmable Plug-in Unit" description="An outlet that can be dimmed." parameters=[] channelTypes=[] ; -ThingType: id="hue:0100" label="Dimmable Light" description="A dimmable light." parameters=[] channelTypes=[] ; -ThingType: id="hue:0000" label="On/Off Light" description="A light that could be switched on and off." parameters=[] channelTypes=[] ; -ThingType: id="hue:0010" label="On/Off Plug-in Unit" description="An outlet that could be switched on and off." parameters=[] channelTypes=[] ; -ThingType: id="hue:0210" label="Extended Color Light" description="A dimmable light with changeable colors and tunable color temperature." parameters=[] channelTypes=[] ; -ThingType: id="hue:bridge" label="Hue Bridge" description="The hue bridge represents the Philips hue bridge." parameters=[] channelTypes=[] ; -ThingType: id="hue:0220" label="Color Temperature Light" description="A dimmable light with tunable color temperature." parameters=[] channelTypes=[] ; -ThingType: id="hue:0200" label="Color Light" description="A dimmable light with changeable colors." parameters=[] channelTypes=[] ; -ThingType: id="androidsensors:android-sensor" label="AndroidSensors" description="Sensors supported by Android" parameters=[] channelTypes=[] ; -ThingType: id="openlicht:skywriter-hat" label="SkyWriterHAT" description="SkyWriterHAT Gesture Recognition" parameters=[] channelTypes=[] ; -ThingType: id="openlicht:polar-m600" label="Polar M600" description="Provides sensor information from a Polar M600 smart watch" parameters=[] channelTypes=[] ; -ThingType: id="openlicht:moto-360" label="Moto 360" description="Provides sensor information from a Moto 360 smart watch" parameters=[] channelTypes=[] ; -ThingType: id="openlicht:samsung-s6" label="Samsung S6" description="Provides sensor information from a smart phone (a Samsung S6 in our case)" parameters=[] channelTypes=[] ; +ThingType: id="hue:0110" label="Dimmable Plug-in Unit" description="An outlet that can be dimmed." ; +ThingType: id="hue:0100" label="Dimmable Light" description="A dimmable light." ; +ThingType: id="hue:0000" label="On/Off Light" description="A light that could be switched on and off." ; +ThingType: id="hue:0010" label="On/Off Plug-in Unit" description="An outlet that could be switched on and off." ; +ThingType: id="hue:0210" label="Extended Color Light" description="A dimmable light with changeable colors and tunable color temperature." ; +ThingType: id="hue:bridge" label="Hue Bridge" description="The hue bridge represents the Philips hue bridge." ; +ThingType: id="hue:0220" label="Color Temperature Light" description="A dimmable light with tunable color temperature." ; +ThingType: id="hue:0200" label="Color Light" description="A dimmable light with changeable colors." ; +ThingType: id="androidsensors:android-sensor" label="AndroidSensors" description="Sensors supported by Android" ; +ThingType: id="openlicht:skywriter-hat" label="SkyWriterHAT" description="SkyWriterHAT Gesture Recognition" ; +ThingType: id="openlicht:polar-m600" label="Polar M600" description="Provides sensor information from a Polar M600 smart watch" ; +ThingType: id="openlicht:moto-360" label="Moto 360" description="Provides sensor information from a Moto 360 smart watch" ; +ThingType: id="openlicht:samsung-s6" label="Samsung S6" description="Provides sensor information from a smart phone (a Samsung S6 in our case)" ; ChannelType: id="hue:color" label="Color" description="The color channel allows to control the color of a light. It is also possible to dim values and switch the light on and off." itemType="Color" category="ColorLight" ; ChannelType: id="hue:brightness" label="Brightness" description="The brightness channel allows to control the brightness of a light. It is also possible to switch the light on and off." itemType="Dimmer" category="DimmableLight" ; ChannelType: id="hue:switch" label="Switch" description="The switch channel allows to switch the light on and off." itemType="Switch" category="Light" ; @@ -52,38 +52,36 @@ ChannelType: id="openlicht:activity-type" label="Activity" description="Current ChannelType: id="openlicht:heart-rate-type" label="Heart Rate" description="Heart rate in beats per minute." itemType="Number" readOnly ; ChannelType: id="openlicht:steps-type" label="Steps" description="Steps run today." itemType="Number" readOnly ; ChannelType: id="openlicht:brightness-type" label="Brightness" description="Brightness (Lux)." itemType="Number" category="Light" readOnly ; -ChannelType: id="system:signal-strength" label="Signal Strength" description="" itemType="Number" category="QualityOfService" readOnly ; -ChannelType: id="system:low-battery" label="Low Battery" description="" itemType="Switch" category="Battery" readOnly ; -ChannelType: id="system:battery-level" label="Battery Level" description="" itemType="Number" category="Battery" readOnly ; -ChannelType: id="system:trigger" label="Trigger" description="" ; -ChannelType: id="system:rawbutton" label="Raw button" description="" ; -ChannelType: id="system:button" label="Button" description="" ; -ChannelType: id="system:rawrocker" label="Raw rocker button" description="" ; -Channel: id="hue:0200:0017880adcf4:5:alert" type="hue:alert" links=[] ; +ChannelType: id="system:signal-strength" label="Signal Strength" itemType="Number" category="QualityOfService" readOnly ; +ChannelType: id="system:low-battery" label="Low Battery" itemType="Switch" category="Battery" readOnly ; +ChannelType: id="system:battery-level" label="Battery Level" itemType="Number" category="Battery" readOnly ; +ChannelType: id="system:trigger" label="Trigger" ; +ChannelType: id="system:rawbutton" label="Raw button" ; +ChannelType: id="system:button" label="Button" ; +ChannelType: id="system:rawrocker" label="Raw rocker button" ; +Channel: id="hue:0200:0017880adcf4:5:alert" type="hue:alert" ; Channel: id="hue:0200:0017880adcf4:5:color" type="hue:color" links=["iris1_item"] ; -Channel: id="hue:0200:0017880adcf4:5:effect" type="hue:effect" links=[] ; -Channel: id="hue:0210:0017880adcf4:1:alert" type="hue:alert" links=[] ; +Channel: id="hue:0200:0017880adcf4:5:effect" type="hue:effect" ; +Channel: id="hue:0210:0017880adcf4:1:alert" type="hue:alert" ; Channel: id="hue:0210:0017880adcf4:1:color" type="hue:color" links=["wohnzimmer_item"] ; -Channel: id="hue:0210:0017880adcf4:1:color_temperature" type="hue:color_temperature" links=[] ; -Channel: id="hue:0210:0017880adcf4:1:effect" type="hue:effect" links=[] ; -Channel: id="hue:0210:0017880adcf4:4:alert" type="hue:alert" links=[] ; +Channel: id="hue:0210:0017880adcf4:1:color_temperature" type="hue:color_temperature" ; +Channel: id="hue:0210:0017880adcf4:1:effect" type="hue:effect" ; +Channel: id="hue:0210:0017880adcf4:4:alert" type="hue:alert" ; Channel: id="hue:0210:0017880adcf4:4:color" type="hue:color" links=["Go1_item"] ; -Channel: id="hue:0210:0017880adcf4:4:color_temperature" type="hue:color_temperature" links=[] ; -Channel: id="hue:0210:0017880adcf4:4:effect" type="hue:effect" links=[] ; +Channel: id="hue:0210:0017880adcf4:4:color_temperature" type="hue:color_temperature" ; +Channel: id="hue:0210:0017880adcf4:4:effect" type="hue:effect" ; Channel: id="openlicht:polar-m600:342dfc32:acceleration-x" type="openlicht:acceleration-type" links=["watch_acceleration_x"] ; Channel: id="openlicht:polar-m600:342dfc32:acceleration-y" type="openlicht:acceleration-type" links=["watch_acceleration_y"] ; Channel: id="openlicht:polar-m600:342dfc32:acceleration-z" type="openlicht:acceleration-type" links=["watch_acceleration_z"] ; -Channel: id="openlicht:polar-m600:342dfc32:activity" type="openlicht:activity-type" links=[] ; +Channel: id="openlicht:polar-m600:342dfc32:activity" type="openlicht:activity-type" ; Channel: id="openlicht:polar-m600:342dfc32:brightness" type="openlicht:brightness-type" links=["polar_brightness"] ; -Channel: id="openlicht:polar-m600:342dfc32:heart-rate" type="openlicht:heart-rate-type" links=[] ; +Channel: id="openlicht:polar-m600:342dfc32:heart-rate" type="openlicht:heart-rate-type" ; Channel: id="openlicht:polar-m600:342dfc32:rotation-x" type="openlicht:rotation-type" links=["watch_rotation_x"] ; -Channel: id="openlicht:polar-m600:342dfc32:rotation-y" type="openlicht:rotation-type" links=["watch_acceleration_y"] ; +Channel: id="openlicht:polar-m600:342dfc32:rotation-y" type="openlicht:rotation-type" links=["watch_rotation_y"] ; Channel: id="openlicht:polar-m600:342dfc32:rotation-z" type="openlicht:rotation-type" links=["watch_rotation_z"] ; -Channel: id="openlicht:polar-m600:342dfc32:steps" type="openlicht:steps-type" links=[] ; +Channel: id="openlicht:polar-m600:342dfc32:steps" type="openlicht:steps-type" ; Channel: id="openlicht:samsung-s6:2ca84896:brightness" type="openlicht:brightness-type" links=["samsung_brightness"] ; Channel: id="openlicht:samsung-s6:2ca84896:rotation-x" type="openlicht:rotation-type" links=["phone_rotation_x"] ; Channel: id="openlicht:samsung-s6:2ca84896:rotation-y" type="openlicht:rotation-type" links=["phone_rotation_y"] ; Channel: id="openlicht:samsung-s6:2ca84896:rotation-z" type="openlicht:rotation-type" links=["phone_rotation_z"] ; Channel: id="openlicht:skywriter-hat:e937d4f3:flick" type="openlicht:flick-type" links=["skywriter_flick_item"] ; -Mqtt: incoming="" outgoing="" host="localhost" ; -Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/tests/hostNonDefaultPort/Test.properties b/eraser-base/src/test/resources/tests/hostNonDefaultPort/Test.properties new file mode 100644 index 0000000000000000000000000000000000000000..616a3deddd4decffd5d0e6e3db7bb8ce3873562a --- /dev/null +++ b/eraser-base/src/test/resources/tests/hostNonDefaultPort/Test.properties @@ -0,0 +1 @@ +result=OUTPUT_PASS diff --git a/eraser-base/src/test/resources/tests/hostNonDefaultPort/description b/eraser-base/src/test/resources/tests/hostNonDefaultPort/description new file mode 100644 index 0000000000000000000000000000000000000000..bdaa31556a7a02ab764f16f5a5157676ce5126bf --- /dev/null +++ b/eraser-base/src/test/resources/tests/hostNonDefaultPort/description @@ -0,0 +1 @@ +Test, if non-default ports in MQTT and Influx are working. diff --git a/eraser-base/src/test/resources/tests/hostNonDefaultPort/input.eraser b/eraser-base/src/test/resources/tests/hostNonDefaultPort/input.eraser new file mode 100644 index 0000000000000000000000000000000000000000..427f6137aa8058df0e264bb6ca8f200eb71dbd7d --- /dev/null +++ b/eraser-base/src/test/resources/tests/hostNonDefaultPort/input.eraser @@ -0,0 +1,2 @@ +Influx: host="www.example.com:1234"; +Mqtt: host="localhost:9876"; diff --git a/eraser-base/src/test/resources/tests/hostNonDefaultPort/output.eraser b/eraser-base/src/test/resources/tests/hostNonDefaultPort/output.eraser new file mode 100644 index 0000000000000000000000000000000000000000..73d8147fea0252ec1c95364ad6ad5449a829b1e6 --- /dev/null +++ b/eraser-base/src/test/resources/tests/hostNonDefaultPort/output.eraser @@ -0,0 +1,2 @@ +Mqtt: host="localhost:9876" ; +Influx: host="www.example.com:1234" ; diff --git a/eraser-base/src/test/resources/tests/ppc1/output.eraser b/eraser-base/src/test/resources/tests/ppc1/output.eraser index 86fdb9c19d9c7b59cb63e8ce62008fe8af115f6b..dfd7ecb410f78ad8ac5c30b1aa6bf119619b55ce 100644 --- a/eraser-base/src/test/resources/tests/ppc1/output.eraser +++ b/eraser-base/src/test/resources/tests/ppc1/output.eraser @@ -1,6 +1,6 @@ Color Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state" ; Group: id="Unknown" items=["iris1_item"] ; -ThingType: id="hue:bridge" label="Hue Bridge" description="The hue bridge represents the Philips hue bridge." parameters=[] channelTypes=[] ; +ThingType: id="hue:bridge" label="Hue Bridge" description="The hue bridge represents the Philips hue bridge." ; 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 ; Parameter: id="brokername" label="Broker Name" description="Name of the broker as defined in the <broker>.url in services/mqtt.cfg. See the MQTT Binding for more information on how to configure MQTT broker connections." type="Text" context="service" default="mosquitto" required ; diff --git a/eraser-base/src/test/resources/tests/ppc3/input.eraser b/eraser-base/src/test/resources/tests/ppc3/input.eraser index d306f014ee1532e73ab194c3b575645830b9388a..9a8a80d0d47d21df28a233b13135c43a98be3f8a 100644 --- a/eraser-base/src/test/resources/tests/ppc3/input.eraser +++ b/eraser-base/src/test/resources/tests/ppc3/input.eraser @@ -14,8 +14,9 @@ Image Item : id="image1" label="an Image Item" state="def" topic="item/str/image Location Item : id="location1" label="a Location Item" state="ghi" topic="item/str/location1/state"; Number Item : id="number1" label="a Number Item" state="456" topic="item/double/number1/state"; Player Item : id="player1" label="a Player Item" state="jkl" topic="item/str/player1/state"; -RollerShutter Item : id="rollerShutter1" label="a RollerShutter Item" state="false" topic="item/str/rs1/state"; +RollerShutter Item : id="rollerShutter1" label="a RollerShutter Item" state="32" topic="item/str/rs1/state"; String Item : id="string1" label="a String Item" state="mno" topic="item/str/string1/state"; Switch Item : id="switch1" label="a Switch Item" state="true" topic="item/bool/switch1/state"; +Switch Item : id="switch2" label="a second Switch Item" state="OFF" topic="item/bool/switch2/state"; Item : id="default1" label="a Default Item" state="pqr" topic="item/str/default1/state"; Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/tests/ppc3/output.eraser b/eraser-base/src/test/resources/tests/ppc3/output.eraser index 686baf62230203f4458bd9297e4d017913f9f341..f480a5d4ecb8144e53e02334acf39b7e6b629868 100644 --- a/eraser-base/src/test/resources/tests/ppc3/output.eraser +++ b/eraser-base/src/test/resources/tests/ppc3/output.eraser @@ -1,19 +1,20 @@ Color Item: id="color1" label="a Color Item" state="1,2,3" topic="item/hsb/color1/state" ; -Contact Item: id="contact1" label="a Contact Item" state="true" topic="item/bool/contact1/state" ; +Contact Item: id="contact1" label="a Contact Item" state="OPEN" topic="item/bool/contact1/state" ; Image Item: id="image1" label="an Image Item" state="def" topic="item/str/image1/state" ; Location Item: id="location1" label="a Location Item" state="ghi" topic="item/str/location1/state" ; -DateTime Item: id="datetime1" label="a DateTime Item" state="1970-01-18T20:43:35.826" topic="item/date/datetime1/state" ; +DateTime Item: id="datetime1" label="a DateTime Item" state="1970-01-18T20:43:35.826Z" topic="item/date/datetime1/state" ; Item: id="default1" label="a Default Item" state="pqr" topic="item/str/default1/state" ; Dimmer Item: id="dimmer1" label="a Dimmer Item" state="123.0" topic="item/double/dimmer1/state" ; Player Item: id="player1" label="a Player Item" state="jkl" topic="item/str/player1/state" ; Number Item: id="number1" label="a Number Item" state="456.0" topic="item/double/number1/state" ; -RollerShutter Item: id="rollerShutter1" label="a RollerShutter Item" state="false" topic="item/str/rs1/state" ; +RollerShutter Item: id="rollerShutter1" label="a RollerShutter Item" state="32.0" topic="item/str/rs1/state" ; String Item: id="string1" label="a String Item" state="mno" topic="item/str/string1/state" ; -Switch Item: id="switch1" label="a Switch Item" state="true" topic="item/bool/switch1/state" ; -Group: id="my-first-group" aggregation="AND" ("ON", "OFF") items=["color1", "contact1", "image1", "location1"] ; +Switch Item: id="switch1" label="a Switch Item" state="ON" topic="item/bool/switch1/state" ; +Switch Item: id="switch2" label="a second Switch Item" state="OFF" topic="item/bool/switch2/state" ; +Group: id="my-first-group" items=["color1", "contact1", "image1", "location1"] aggregation="AND" ("ON", "OFF") ; Group: id="my-second-group" items=["datetime1", "default1"] ; Group: id="my-third-group" items=["dimmer1", "player1"] ; Group: id="my-empty-group" ; -Group: id="Unknown" items=["number1", "rollerShutter1", "string1", "switch1"] ; +Group: id="Unknown" items=["number1", "rollerShutter1", "string1", "switch1", "switch2"] ; Mqtt: incoming="ppc3/" outgoing="oh2/in/" host="localhost" ; Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/tests/ppc4/Test.properties b/eraser-base/src/test/resources/tests/ppc4/Test.properties new file mode 100644 index 0000000000000000000000000000000000000000..616a3deddd4decffd5d0e6e3db7bb8ce3873562a --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc4/Test.properties @@ -0,0 +1 @@ +result=OUTPUT_PASS diff --git a/eraser-base/src/test/resources/tests/ppc4/description b/eraser-base/src/test/resources/tests/ppc4/description new file mode 100644 index 0000000000000000000000000000000000000000..0140c1516a0dea41a1fc44a850990d52c167309b --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc4/description @@ -0,0 +1 @@ +Test item types and controlling items. diff --git a/eraser-base/src/test/resources/tests/ppc4/input.eraser b/eraser-base/src/test/resources/tests/ppc4/input.eraser new file mode 100644 index 0000000000000000000000000000000000000000..8687f881951aa3bc8766e209a87432a235e2a72a --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc4/input.eraser @@ -0,0 +1,22 @@ +Mqtt: incoming="ppc3/" outgoing="oh2/in/" host="localhost" ; + +Group: id="my-first-group" aggregation="AND" ("ON", "OFF") items=["color1", "contact1", "image1", "location1"] ; +Group: id="my-second-group" items=["datetime1", "default1"] ; +Group: id="my-third-group" items=["dimmer1", "player1"] ; + +Group: id="my-empty-group" ; + +Color Item : id="color1" label="a Color Item" state="1,2,3" topic="item/hsb/color1/state"; +DateTime Item : id="datetime1" label="a DateTime Item" state="1543415826" topic="item/date/datetime1/state"; +Contact Item : id="contact1" label="a Contact Item" state="CLOSED" topic="item/bool/contact1/state"; +Dimmer Item : id="dimmer1" label="a Dimmer Item" state="123" topic="item/double/dimmer1/state" controls=["color1", "datetime1"]; +Image Item : id="image1" label="an Image Item" state="def" topic="item/str/image1/state" controls=[]; +Location Item : id="location1" label="a Location Item" state="ghi" topic="item/str/location1/state"; +Number Item : id="number1" label="a Number Item" state="456" topic="item/double/number1/state" controls=["string1"]; +Player Item : id="player1" label="a Player Item" state="jkl" topic="item/str/player1/state"; +RollerShutter Item : id="rollerShutter1" label="a RollerShutter Item" state="0" topic="item/str/rs1/state"; +Activity Item: id="activity"; +String Item : id="string1" label="a String Item" state="mno" topic="item/str/string1/state"; +Switch Item : id="switch1" label="a Switch Item" state="true" topic="item/bool/switch1/state" controls=["rollerShutter1"]; +Item : id="default1" label="a Default Item" state="pqr" topic="item/str/default1/state"; +Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/tests/ppc4/output.eraser b/eraser-base/src/test/resources/tests/ppc4/output.eraser new file mode 100644 index 0000000000000000000000000000000000000000..d4934300c2f7e3c0c8be65bf4f4932bbc4b7be52 --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc4/output.eraser @@ -0,0 +1,20 @@ +Color Item: id="color1" label="a Color Item" state="1,2,3" topic="item/hsb/color1/state" ; +Contact Item: id="contact1" label="a Contact Item" state="CLOSED" topic="item/bool/contact1/state" ; +Image Item: id="image1" label="an Image Item" state="def" topic="item/str/image1/state" ; +Location Item: id="location1" label="a Location Item" state="ghi" topic="item/str/location1/state" ; +DateTime Item: id="datetime1" label="a DateTime Item" state="1970-01-18T20:43:35.826Z" topic="item/date/datetime1/state" ; +Item: id="default1" label="a Default Item" state="pqr" topic="item/str/default1/state" ; +Dimmer Item: id="dimmer1" label="a Dimmer Item" state="123.0" topic="item/double/dimmer1/state" controls=["color1", "datetime1"] ; +Player Item: id="player1" label="a Player Item" state="jkl" topic="item/str/player1/state" ; +Number Item: id="number1" label="a Number Item" state="456.0" topic="item/double/number1/state" controls=["string1"] ; +RollerShutter Item: id="rollerShutter1" label="a RollerShutter Item" state="0.0" topic="item/str/rs1/state" ; +Activity Item: id="activity" ; +String Item: id="string1" label="a String Item" state="mno" topic="item/str/string1/state" ; +Switch Item: id="switch1" label="a Switch Item" state="ON" topic="item/bool/switch1/state" controls=["rollerShutter1"] ; +Group: id="my-first-group" items=["color1", "contact1", "image1", "location1"] aggregation="AND" ("ON", "OFF") ; +Group: id="my-second-group" items=["datetime1", "default1"] ; +Group: id="my-third-group" items=["dimmer1", "player1"] ; +Group: id="my-empty-group" ; +Group: id="Unknown" items=["number1", "rollerShutter1", "activity", "string1", "switch1"] ; +Mqtt: incoming="ppc3/" outgoing="oh2/in/" host="localhost" ; +Influx: host="localhost" ; diff --git a/eraser-base/src/test/resources/tests/ppc5/Test.properties b/eraser-base/src/test/resources/tests/ppc5/Test.properties new file mode 100644 index 0000000000000000000000000000000000000000..616a3deddd4decffd5d0e6e3db7bb8ce3873562a --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc5/Test.properties @@ -0,0 +1 @@ +result=OUTPUT_PASS diff --git a/eraser-base/src/test/resources/tests/ppc5/description b/eraser-base/src/test/resources/tests/ppc5/description new file mode 100644 index 0000000000000000000000000000000000000000..ff98c287292d9b2a62174084a242cf6e4d25c012 --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc5/description @@ -0,0 +1,2 @@ +Complete feature-test, i.e., minimal and maximal many members set for each parseable non-terminal. +Also test, if multi-line statements work. diff --git a/eraser-base/src/test/resources/tests/ppc5/input.eraser b/eraser-base/src/test/resources/tests/ppc5/input.eraser new file mode 100644 index 0000000000000000000000000000000000000000..21cc14bc796965aae71d2bca119f60b0cdf640c5 --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc5/input.eraser @@ -0,0 +1,35 @@ +ChannelType: id="min-channel-type"; +ChannelType: label="Max Channel Type 1" id="max-channel-type-1" itemType="String" description="Channel type with all members set" category="Text" ; +ChannelType: description="Channel type with all members set" id="max-channel-type-2" readOnly itemType="String" label="Max Channel Type 2" category="CustomCategory" ; + +// Channels will get sorted alphabetically in output +Channel: type="min-channel-type" id="min-channel" ; // "type" must be set +Channel: links=["min-item", "max-item"] id="max-channel" type="max-channel-type-1" ; + +Group: id="min-group" ; +Group: items=["min-item", "max-item"] id="max-group" groups=["min-group"] aggregation="AND" ("one", "two") ; + +Number Item: id="min-item" ; // state will be set to default value +Switch Item: topic="items/max" + controls=["min-item"] id="max-item" state="true" label="Item with all members set" category="not used" + metaData={"one":"true", "zero":"false"} ; + +// Parameters will get sorted alphabetically in output +Parameter: id="min-parameter" ; +Parameter +: +label="Max Parameter" required id="max-parameter" type="Decimal" description="Parameter with all members set" default="should be a number" ; + +ThingType: id="min-thing-type" ; +ThingType: id="max-thing-type" label="Max Thing Type" description="Thing type with all members set" parameters=["min-parameter", "max-parameter"] channelTypes=["min-channel-type", "max-channel-type-1", "max-channel-type-2"] ; + +Thing: type="min-thing-type" id="min-thing" ; // "type" must be set +Thing: id="max-thing" label="Max Thing" type="max-thing-type" channels=["min-channel", "max-channel"] ; + +Influx: ; // minimal Influx, most probably will fail in an actual system +Mqtt: ; // minimal MQTT, most probably will fail in an actual system + +ML: activities = { + 0 : "an interesting activity", + 3: "rather boring activity" +} ; diff --git a/eraser-base/src/test/resources/tests/ppc5/output.eraser b/eraser-base/src/test/resources/tests/ppc5/output.eraser new file mode 100644 index 0000000000000000000000000000000000000000..5498db37c271084736a1be692b318f92ab2c5b5e --- /dev/null +++ b/eraser-base/src/test/resources/tests/ppc5/output.eraser @@ -0,0 +1,16 @@ +Thing: id="min-thing" type="min-thing-type" ; +Thing: id="max-thing" label="Max Thing" type="max-thing-type" channels=["min-channel", "max-channel"] ; +Number Item: id="min-item" state="0.0" ; +Switch Item: id="max-item" label="Item with all members set" state="ON" category="not used" topic="items/max" controls=["min-item"] metaData={"one":"true", "zero":"false"} ; +Group: id="min-group" ; +Group: id="max-group" groups=["min-group"] items=["min-item", "max-item"] aggregation="AND" ("one", "two") ; +ThingType: id="min-thing-type" ; +ThingType: id="max-thing-type" label="Max Thing Type" description="Thing type with all members set" parameters=["min-parameter", "max-parameter"] channelTypes=["min-channel-type", "max-channel-type-1", "max-channel-type-2"] ; +ChannelType: id="min-channel-type" ; +ChannelType: id="max-channel-type-1" label="Max Channel Type 1" description="Channel type with all members set" itemType="String" category="Text" ; +ChannelType: id="max-channel-type-2" label="Max Channel Type 2" description="Channel type with all members set" itemType="String" category="CustomCategory" readOnly ; +Parameter: id="max-parameter" label="Max Parameter" description="Parameter with all members set" type="Decimal" default="should be a number" required ; +Parameter: id="min-parameter" ; +Channel: id="max-channel" type="max-channel-type-1" links=["min-item", "max-item"] ; +Channel: id="min-channel" type="min-channel-type" ; +ML: activities={0:"an interesting activity", 3:"rather boring activity"} ; diff --git a/eraser.rest/.gitignore b/eraser.rest/.gitignore index 84c048a73cc2e5dd24f807669eb99b0ce3123195..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/eraser.rest/.gitignore +++ b/eraser.rest/.gitignore @@ -1 +1,3 @@ /build/ +/bin/ +logs/ diff --git a/eraser.rest/build.gradle b/eraser.rest/build.gradle index 0f7c52ef57d6d2c0100b9bb34e0473c5f4a05e6b..0ac26261047eacbcc2b97709e7a5b4c1bc927b3b 100644 --- a/eraser.rest/build.gradle +++ b/eraser.rest/build.gradle @@ -1,37 +1,22 @@ buildscript { - repositories { - mavenCentral() - } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.2.RELEASE") } } plugins { - id 'java' id 'io.franzbecker.gradle-lombok' version '1.14' } -repositories { - mavenCentral() -} - apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' -sourceCompatibility = 1.8 - dependencies { compile project(':eraser-base') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' compile 'org.springframework.boot:spring-boot-starter-web' - compile 'io.springfox:springfox-swagger2:2.9.2' - compile 'io.springfox:springfox-swagger-ui:2.9.2' + compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2' + compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2' testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } sourceSets { diff --git a/eraser.rest/src/main/resources/log4j2.xml b/eraser.rest/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/eraser.rest/src/main/resources/log4j2.xml +++ b/eraser.rest/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/eraser.spark/.gitignore b/eraser.spark/.gitignore index 84c048a73cc2e5dd24f807669eb99b0ce3123195..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/eraser.spark/.gitignore +++ b/eraser.spark/.gitignore @@ -1 +1,3 @@ /build/ +/bin/ +logs/ diff --git a/eraser.spark/build.gradle b/eraser.spark/build.gradle index fb3151d88260b4899ed862101a54b3866d9d748f..5a577d15400aab9d58ac2fec4fae8589b0995020 100644 --- a/eraser.spark/build.gradle +++ b/eraser.spark/build.gradle @@ -1,24 +1,12 @@ plugins { - id 'java' id 'application' - id 'io.franzbecker.gradle-lombok' version '1.14' + id 'io.franzbecker.gradle-lombok' version '3.0.0' } -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - dependencies { compile project(':eraser-base') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.11.1' - compile 'com.sparkjava:spark-core:2.7.2' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' + compile group: 'com.sparkjava', name: 'spark-core', version: '2.9.0' } run { diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java index 6bc66c1e0dd89cbfb8a7ee7523857e970bbffbd3..4d8df7dfaa448fe34c6cef1ed51a30d4dd2bc1ae 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java @@ -3,13 +3,20 @@ package de.tudresden.inf.st.eraser.spark; import com.fasterxml.jackson.databind.ObjectMapper; import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.util.JavaUtils; +import de.tudresden.inf.st.eraser.util.ParserUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import spark.Request; import spark.Response; import spark.Spark; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; +import java.util.function.Function; +import java.util.stream.Collectors; /** * The main class to start the rest service. @@ -18,47 +25,133 @@ import java.util.concurrent.locks.Lock; */ public class Application { - private final Root model; + private static final Logger logger = LogManager.getLogger(Application.class); + private final Root root; private final Lock lock; private final Condition quitCondition; - private Application(Root model, Lock lock, Condition quitCondition) { - this.model = model; + private Application(Root root, Lock lock, Condition quitCondition) { + this.root = root; this.lock = lock; this.quitCondition = quitCondition; } private void createRules() { ObjectMapper mapper = new ObjectMapper(); - Spark.get("/items/", (request, response) -> - model.items(), - mapper::writeValueAsString); - - Spark.get("/activity/", - (request, response) -> wrapActivityList(model.getMachineLearningRoot().getActivityList()), - mapper::writeValueAsString); - Spark.get("/activity/current", - (request, response) -> JavaUtils.ifPresentOrElseReturn(model.currentActivity(), - this::wrapActivity, - () -> makeError(response, 204, "No activity recognized.")), - mapper::writeValueAsString); - Spark.get("/activity/:identifier", - (request, response) -> - JavaUtils.ifPresentOrElseReturn(model.resolveActivity(paramAsInt(request, "identifier")), - this::wrapActivity, - () -> makeError(response, 404, "No activity for identifier " + request.params("identifier"))), - mapper::writeValueAsString); - - Spark.get("/events/", - (request, response) -> wrapChangeEventList(model.getMachineLearningRoot().getChangeEventList()), - mapper::writeValueAsString); - Spark.get("/events/:identifier", - (request, response) -> - JavaUtils.ifPresentOrElseReturn(model.resolveChangeEvent(paramAsInt(request, "identifier")), - this::wrapChangeEvent, - () -> makeError(response, 404, "No event for identifier " + request.params("identifier"))), - mapper::writeValueAsString); + Spark.path("/", () -> Spark.before((request, response) -> logger.debug("{}: {}", request.pathInfo(), request.body()))); + + Spark.path("/activity", () -> { + + //--- GET /activity --- + Spark.get("", + (request, response) -> wrapActivityList(root.getMachineLearningRoot().getActivityList()), + mapper::writeValueAsString); + + //--- GET /activity/current --- + Spark.get("/current", + (request, response) -> JavaUtils.ifPresentOrElseReturn(root.currentActivity(), + this::wrapActivity, + () -> makeError(response, 204, "No activity recognized.")), + mapper::writeValueAsString); + + //--- PUT /activity/current --- + Spark.put("/current", (request, response) -> { + logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength()); + if (!root.getMachineLearningRoot().hasActivityRecognition()) { + return makeError(response, 404, "No activity recognition model found"); + } + MachineLearningModel activityRecognition = root.getMachineLearningRoot().getActivityRecognition(); + if (activityRecognition.canSetActivity()) { + activityRecognition.setActivity(Integer.valueOf(request.body())); + return "OK"; + } else { + return makeError(response, 501, "Activity not editable for " + activityRecognition.getClass().getSimpleName()); + } + }); + + //--- GET /activity/:identifier --- + Spark.get("/:identifier", + (request, response) -> + JavaUtils.ifPresentOrElseReturn(root.resolveActivity(paramAsInt(request, "identifier")), + this::wrapActivity, + () -> makeError(response, 404, "No activity for identifier " + request.params("identifier"))), + mapper::writeValueAsString); + }); + + Spark.path("/events", () -> { + + //--- GET /events --- + Spark.get("", + (request, response) -> wrapChangeEventList(root.getMachineLearningRoot().getChangeEventList()), + mapper::writeValueAsString); + + //--- GET /events/:identifier --- + Spark.get("/:identifier", + (request, response) -> + JavaUtils.ifPresentOrElseReturn(root.resolveChangeEvent(paramAsInt(request, "identifier")), + this::wrapChangeEvent, + () -> makeError(response, 404, "No event for identifier " + request.params("identifier"))), + mapper::writeValueAsString); + }); + + Spark.path("/model", () -> { + + //--- GET /model/full --- + Spark.get("/full", (request, response) -> { + response.type("text/plain"); + return root.prettyPrint(); + }); + Spark.path("/items", () -> { + + //--- GET /model/items --- + Spark.get("", + (request, response) -> wrapItemList(root.getOpenHAB2Model().items()), + mapper::writeValueAsString); + Spark.path("/:identifier", () -> { + + Spark.put("", (request, response) -> { + OpenHAB2Model openHAB2Model = root.getOpenHAB2Model(); + Item item = ParserUtils.parseItem(request.body()); + if (!openHAB2Model.resolveItem(item.getID()).isPresent()) { + root.getOpenHAB2Model().addNewItem(item); + response.status(201); + return "OK"; + } else { + return makeError(response, 409, "Item already exists."); + } + }); + + //--- GET /model/items/:identifier/state --- + Spark.get("/state", (request, response) -> + safeItemRoute(request, response, Item::getStateAsString)); + + //--- PUT /model/items/:identifier/state --- + Spark.put("/state", (request, response) -> { + logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength()); + return safeItemRoute(request, response, + item -> { + try { + item.setStateFromString(request.body()); + return "OK"; + } catch (Exception e) { + logger.catching(e); + return makeError(response, 500, e.getMessage()); + } + }); + }); + }); + + //--- GET /model/items/:identifier/history --- + Spark.get("/:identifier/history", + (request, response) -> { + logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength()); + return safeItemRoute(request, response, item -> makeHistory(item, response)); + }); + }); + }); + + //--- POST /system/exit --- Spark.post("/system/exit", (request, response) -> { try { lock.lock(); @@ -71,6 +164,36 @@ public class Application { }); } + private Object safeItemRoute(Request request, Response response, Function<Item, String> action) { + return JavaUtils.ifPresentOrElseReturn(root.getOpenHAB2Model().resolveItem(request.params("identifier")), action, + () -> makeError(response, 404, "Item '" + request.params("identifier") + "' not found")); + } + + private String makeHistory(Item item, Response response) { + response.type("text/plain"); + InfluxAdapter influxAdapter = root.getInfluxRoot().influxAdapter(); + influxAdapter.disableAsyncQuery(); + List<? extends AbstractItemPoint> list; + if (item.asColorItem() != null) { + list = item.asColorItem().getHistory(); + } else if (item.asDateTimeItem() != null) { + list = item.asDateTimeItem().getHistory(); + } else if (item.asItemWithBooleanState() != null) { + list = item.asItemWithBooleanState().getHistory(); + } else if (item.asItemWithDoubleState() != null) { + list = item.asItemWithDoubleState().getHistory(); + } else if (item.asItemWithStringState() != null) { + list = item.asItemWithStringState().getHistory(); + } else { + String message = "Can not make history for item of unknown type."; + logger.warn(message); + influxAdapter.enableAsyncQuery(); + return message; + } + influxAdapter.enableAsyncQuery(); + return list.stream().map(AbstractItemPoint::toString).collect(Collectors.joining("\n")); + } + private String makeError(Response response, int status_code, String message) { response.status(status_code); return message; @@ -89,31 +212,55 @@ public class Application { } private List<SimpleActivity> wrapActivityList(JastAddList<Activity> activities) { - List<SimpleActivity> result = new ArrayList<>(activities.getNumChild()); - for (Activity activity : activities) { - result.add(wrapActivity(activity)); - } - return result; + return JavaUtils.toStream(activities).map(this::wrapActivity).collect(Collectors.toList()); } private List<SimpleChangeEvent> wrapChangeEventList(JastAddList<ChangeEvent> events) { - List<SimpleChangeEvent> result = new ArrayList<>(events.getNumChild()); - for (ChangeEvent event : events) { - result.add(wrapChangeEvent(event)); + return JavaUtils.toStream(events).map(this::wrapChangeEvent).collect(Collectors.toList()); + } + + private SimpleItem wrapItem(Item item) { + return SimpleItem.of(item.getID(), + item.getLabel(), + item.getTopic() != null ? item.getTopic().getTopicString() : null, + item.isFrozen(), + item.isSendState(), + item.getControllingList().stream().map(Item::getID).collect(Collectors.toList()), + wrapMetaData(item.getMetaDataList())); + } + + private Map<String, String> wrapMetaData(JastAddList<ItemMetaData> itemMetaDataList) { + Map<String, String> result = new HashMap<>(); + for (ItemMetaData metaData : itemMetaDataList) { + result.put(metaData.getKey(), metaData.getValue()); } return result; } + private List<SimpleItem> wrapItemList(List<Item> items) { + return items.stream().map(this::wrapItem).collect(Collectors.toList()); + } + private int paramAsInt(Request request, String paramName) { return Integer.parseInt(request.params(paramName)); } public static void start(int port, Root model, boolean createDummyMLData, Lock lock, Condition quit) { - Spark.port(port); - Application application = new Application(model, lock, quit); - if (createDummyMLData) { - new DummyDataCreator(model).addDummyDataToModel(); + try { + Spark.port(port); + Application application = new Application(model, lock, quit); + if (createDummyMLData) { + new DummyDataCreator(model).addDummyDataToModel(); + } + application.createRules(); + } catch (Exception e) { + logger.catching(e); + try { + lock.lock(); + quit.signalAll(); + } finally { + lock.unlock(); + } } - application.createRules(); } } diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/DummyDataCreator.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/DummyDataCreator.java index f0ae79d88b5695a778370127ba23394b51c5b81c..65885823f7960465adadf30bbda4f82744c54903 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/DummyDataCreator.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/DummyDataCreator.java @@ -3,6 +3,9 @@ package de.tudresden.inf.st.eraser.spark; import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.util.JavaUtils; +import java.time.Duration; +import java.time.Instant; + /** * Creates some activities and change events. * @@ -10,56 +13,65 @@ import de.tudresden.inf.st.eraser.util.JavaUtils; */ class DummyDataCreator { - private final Root model; + private final Root root; - DummyDataCreator(Root model) { - this.model = model; + DummyDataCreator(Root root) { + this.root = root; } private void addDummyActivitiesToModel() { - MachineLearningRoot mlRoot = model.getMachineLearningRoot(); + MachineLearningRoot mlRoot = root.getMachineLearningRoot(); mlRoot.addActivity(new Activity(1, "Sitting in armchair")); mlRoot.addActivity(new Activity(2, "Going to sleep")); mlRoot.addActivity(new Activity(3, "Entering house")); } private void addDummyChangeEventsToModel() { - MachineLearningRoot mlRoot = model.getMachineLearningRoot(); + MachineLearningRoot mlRoot = root.getMachineLearningRoot(); Item iris1 = getOrCreateColorItem("iris1", "Hue Iris 1"); Item go1 = getOrCreateColorItem("go1", "Hue Go 1"); Item go2 = getOrCreateColorItem("go2", "Hue Go 2"); - mlRoot.addChangeEvent(newRecognitionEvent(1, 1547637740, 1, + Instant now = Instant.now(); + mlRoot.addChangeEvent(newRecognitionEvent(1, now, 1, new ChangedItem("green", iris1), new ChangedItem("green", go1))); - mlRoot.addChangeEvent(newRecognitionEvent(2, 1547637750, 1)); - mlRoot.addChangeEvent(newRecognitionEvent(4, 1547623460, 2, + mlRoot.addChangeEvent(newRecognitionEvent(2, now.plusSeconds(1), 1)); + mlRoot.addChangeEvent(newRecognitionEvent(3, now.plusSeconds(3), 2, new ChangedItem("off", go2))); - mlRoot.addChangeEvent(newManualChangeEvent(1501146256, 5, + mlRoot.addChangeEvent(newManualChangeEvent(4, now.plusSeconds(17), new ChangedItem("green", iris1), new ChangedItem("red", go1), new ChangedItem("#EE7F00", go2))); } private Item getOrCreateColorItem(String name, String label) { - return model.resolveItem(name).orElseGet(() -> new ColorItem(name, label, true, false, TupleHSB.of(0, 0, 0))); + return root.getOpenHAB2Model().resolveItem(name).orElseGet(() -> { + ColorItem result = new ColorItem(); + result.setID(name); + result.setLabel(label); + result.disableSendState(); + result.setState(TupleHSB.of(0, 0, 0)); + result.enableSendState(); + return result; + }); } - private RecognitionEvent newRecognitionEvent(int identifier, long timestamp, int activityIdentifier, ChangedItem... changedItems) { + private RecognitionEvent newRecognitionEvent(int identifier, Instant when, int activityIdentifier, ChangedItem... changedItems) { RecognitionEvent result = new RecognitionEvent(); - JavaUtils.ifPresentOrElse(model.resolveActivity(activityIdentifier), result::setActivity, () -> { throw new RuntimeException("No activity found for identifier " + activityIdentifier); }); - initChangeEvent(result, identifier, timestamp, changedItems); + JavaUtils.ifPresentOrElse(root.resolveActivity(activityIdentifier), result::setActivity, () -> { throw new RuntimeException("No activity found for identifier " + activityIdentifier); }); + initChangeEvent(result, identifier, when, changedItems); return result; } @SuppressWarnings("SameParameterValue") - private ManualChangeEvent newManualChangeEvent(int identifier, long timestamp, ChangedItem... changedItems) { + private ManualChangeEvent newManualChangeEvent(int identifier, Instant when, ChangedItem... changedItems) { ManualChangeEvent result = new ManualChangeEvent(); - initChangeEvent(result, identifier, timestamp, changedItems); + initChangeEvent(result, identifier, when, changedItems); return result; } - private void initChangeEvent(ChangeEvent result, int identifier, long timestamp, ChangedItem... changedItems) { + private void initChangeEvent(ChangeEvent result, int identifier, Instant when, ChangedItem... changedItems) { result.setIdentifier(identifier); - result.setTimestamp(timestamp); + result.setCreated(when); for (ChangedItem changedItem : changedItems) { result.addChangedItem(changedItem); } diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleActivity.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleActivity.java index 5d914d8a8da8f3aab9cd68c92af5ba3235141460..826463ab6e80794a694852942560b5b886f36bed 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleActivity.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleActivity.java @@ -1,9 +1,11 @@ package de.tudresden.inf.st.eraser.spark; -//import io.swagger.annotations.ApiModelProperty; import de.tudresden.inf.st.eraser.jastadd.model.Activity; import lombok.Data; +import java.util.Collections; +import java.util.List; + /** * A recognizable activity. * @@ -11,12 +13,11 @@ import lombok.Data; */ @Data(staticConstructor = "of") public class SimpleActivity { -// @ApiModelProperty(notes = "Some identifier of this activity") public final int identifier; -// @ApiModelProperty(notes = "Name of the activity") public final String description; + public final List<String> items; static SimpleActivity createFrom(Activity activity) { - return SimpleActivity.of(activity.getIdentifier(), activity.getLabel()); + return SimpleActivity.of(activity.getIdentifier(), activity.getLabel(), Collections.emptyList()); } } diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleChangeEvent.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleChangeEvent.java index 07894ede8b266b46bf4aa364bb4495d07abc8740..6a1c1a91bc3d8cdc28d44b07b766168e645786bf 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleChangeEvent.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleChangeEvent.java @@ -3,6 +3,7 @@ package de.tudresden.inf.st.eraser.spark; import lombok.AllArgsConstructor; import lombok.Data; +import java.time.Instant; import java.util.List; /** @@ -13,7 +14,7 @@ import java.util.List; @Data @AllArgsConstructor public abstract class SimpleChangeEvent { - public final long timestamp; + public final Instant created; public final int identifier; public final List<SimpleChangedItem> changed_items; } diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleItem.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleItem.java new file mode 100644 index 0000000000000000000000000000000000000000..2eac624367215c547ef4cfe0058fdcce337062fa --- /dev/null +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleItem.java @@ -0,0 +1,22 @@ +package de.tudresden.inf.st.eraser.spark; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * Safe representation of an item + * + * @author rschoene - Initial contribution + */ +@Data(staticConstructor = "of") +public class SimpleItem { + public final String ID; + public final String label; + public final String topic; + public final boolean frozen; + public final boolean sendState; + public final List<String> controlling; + public final Map<String, String> metaData; +} diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleManualChangeEvent.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleManualChangeEvent.java index 71e43cdbbcce6eb65704d280540220db880ed3b6..685436cd376b993403db98d4a6192b4de94975d5 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleManualChangeEvent.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleManualChangeEvent.java @@ -5,6 +5,7 @@ import de.tudresden.inf.st.eraser.util.JavaUtils; import lombok.Data; import lombok.EqualsAndHashCode; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -18,12 +19,12 @@ import java.util.stream.Collectors; public class SimpleManualChangeEvent extends SimpleChangeEvent { public final String type = "manual"; - public SimpleManualChangeEvent(long timestamp, int identifier, List<SimpleChangedItem> changedItems) { - super(timestamp, identifier, changedItems); + public SimpleManualChangeEvent(Instant created, int identifier, List<SimpleChangedItem> changedItems) { + super(created, identifier, changedItems); } static SimpleManualChangeEvent createFrom(ManualChangeEvent event) { - return new SimpleManualChangeEvent(event.getTimestamp(), event.getIdentifier(), + return new SimpleManualChangeEvent(event.getCreated(), event.getIdentifier(), JavaUtils.toStream(event.getChangedItems()).map(SimpleChangedItem::createFrom).collect(Collectors.toList())); } } diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleRecognitionEvent.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleRecognitionEvent.java index 587c3ae127da7cf3be1064a4351e36f6bbf81e7d..0a3821e4fc49d9fa2b2d0e75717da5477c0a3fa7 100644 --- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleRecognitionEvent.java +++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/SimpleRecognitionEvent.java @@ -5,6 +5,7 @@ import de.tudresden.inf.st.eraser.util.JavaUtils; import lombok.Data; import lombok.EqualsAndHashCode; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -20,14 +21,14 @@ public class SimpleRecognitionEvent extends SimpleChangeEvent { public final String description; public final String type = "recognition"; - public SimpleRecognitionEvent(long timestamp, int identifier, List<SimpleChangedItem> changedItems, int activity, String description) { - super(timestamp, identifier, changedItems); + public SimpleRecognitionEvent(Instant created, int identifier, List<SimpleChangedItem> changedItems, int activity, String description) { + super(created, identifier, changedItems); this.activity = activity; this.description = description; } static SimpleRecognitionEvent createFrom(RecognitionEvent event) { - return new SimpleRecognitionEvent(event.getTimestamp(), event.getIdentifier(), + return new SimpleRecognitionEvent(event.getCreated(), event.getIdentifier(), JavaUtils.toStream(event.getChangedItems()).map(SimpleChangedItem::createFrom).collect(Collectors.toList()), event.getActivity().getIdentifier(), event.getActivity().getLabel()); } diff --git a/eraser.spark/src/main/resources/log4j2.xml b/eraser.spark/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/eraser.spark/src/main/resources/log4j2.xml +++ b/eraser.spark/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/eraser.starter/.gitignore b/eraser.starter/.gitignore index 84c048a73cc2e5dd24f807669eb99b0ce3123195..54c5c0b024dab04da2a43d4135ed67ed2a670b46 100644 --- a/eraser.starter/.gitignore +++ b/eraser.starter/.gitignore @@ -1 +1,2 @@ /build/ +logs/ diff --git a/eraser.starter/build.gradle b/eraser.starter/build.gradle index fe1e38b9e989fbf38d0ddfa58068b6c78264d97a..186e75a1e448ab9f0e61c5f8df2d3ac94da2f1f9 100644 --- a/eraser.starter/build.gradle +++ b/eraser.starter/build.gradle @@ -1,11 +1,9 @@ -//buildscript { -// repositories { -// mavenCentral() -// } -// dependencies { -// classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.2.RELEASE") -// } -//} +plugins { + id 'java' + id 'application' + id 'distribution' + id 'io.github.http-builder-ng.http-plugin' version '0.1.1' +} repositories { mavenCentral() @@ -13,37 +11,39 @@ repositories { sourceCompatibility = 1.8 -apply plugin: 'java' -apply plugin: 'application' -//apply plugin: 'org.springframework.boot' -//apply plugin: 'io.spring.dependency-management' - dependencies { compile project(':eraser-base') -// compile project(':eraser.rest') compile project(':eraser.spark') compile project(':feedbackloop.api') compile project(':feedbackloop.analyze') compile project(':feedbackloop.plan') compile project(':feedbackloop.execute') -// compile 'org.springframework.boot:spring-boot-starter-web' - compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.8' - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8' -// compile 'org.apache.commons:commons-lang3:8.1' + compile project(':feedbackloop.learner_backup') + compile project(':datasets') + compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.9.8' compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'net.sourceforge.argparse4j:argparse4j:0.8.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'net.sourceforge.argparse4j', name: 'argparse4j', version: '0.8.1' + // compile project(':feedbackloop.learner') + } run { mainClassName = 'de.tudresden.inf.st.eraser.starter.EraserStarter' standardInput = System.in -// if (project.hasProperty("appArgs")) { -// args Eval.me(appArgs) -// } +} + +import io.github.httpbuilderng.http.HttpTask + +task shutdown (type: HttpTask) { + group = 'application' + description = 'Shuts down a running eraser application' + + config { + request.uri = 'http://localhost:4567' + } + post { + request.uri.path = '/system/exit' + } } sourceSets { @@ -53,3 +53,19 @@ sourceSets { } } } + +//distributions { +// main { +// contents { +// from { +// 'src/main/resources/starter.eraser' +// } +// } +// } +//} +applicationDistribution.from("src/main/resources") { + include "starter.eraser" +} +applicationDistribution.from(".") { + include "starter-setting.yaml" +} diff --git a/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/EraserStarter.java b/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/EraserStarter.java index ec147f0bb07e89363bec552207e5b80a418a83c9..acc4b93a471d0b3a02676d37cb0314c85756ed1c 100644 --- a/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/EraserStarter.java +++ b/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/EraserStarter.java @@ -8,11 +8,18 @@ import de.tudresden.inf.st.eraser.feedbackloop.api.Analyze; import de.tudresden.inf.st.eraser.feedbackloop.api.Execute; import de.tudresden.inf.st.eraser.feedbackloop.api.Plan; import de.tudresden.inf.st.eraser.feedbackloop.execute.ExecuteImpl; +import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.Learner; +import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.MachineLearningImpl; import de.tudresden.inf.st.eraser.feedbackloop.plan.PlanImpl; import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.openhab2.OpenHab2Importer; +import de.tudresden.inf.st.eraser.openhab2.mqtt.MQTTUpdater; import de.tudresden.inf.st.eraser.spark.Application; import de.tudresden.inf.st.eraser.util.ParserUtils; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.annotation.Arg; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -20,6 +27,8 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -36,15 +45,30 @@ import java.util.concurrent.locks.ReentrantLock; */ public class EraserStarter { + private static class CommandLineOptions { + @Arg(dest = "config_file") + String configFile; + } + private static final Logger logger = LogManager.getLogger(EraserStarter.class); -// private static final String NO_MAPE = "no_mape"; -// public static final String NO_REST = "no_rest"; @SuppressWarnings("ResultOfMethodCallIgnored") public static void main(String[] args) { - logger.info("Starting ERASER"); + ArgumentParser parser = ArgumentParsers.newFor("eraser").build() + .defaultHelp(true) + .description("Starts the knowledge-base of OpenLicht"); + parser.addArgument("-f", "--config-file") + .help("Path to the configuration YAML file") + .setDefault("starter-setting.yaml"); + CommandLineOptions commandLineOptions = new CommandLineOptions(); + try { + parser.parseArgs(args, commandLineOptions); + } catch (ArgumentParserException e) { + parser.handleError(e); + System.exit(1); + } ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - File settingsFile = new File("starter-setting.yaml"); + File settingsFile = new File(commandLineOptions.configFile); Setting settings; try { settings = mapper.readValue(settingsFile, Setting.class); @@ -54,44 +78,145 @@ public class EraserStarter { System.exit(1); return; } - String openhabUrl = settings.openHabUrl; - String file = settings.file; + logger.info("Starting ERASER"); boolean startRest = settings.rest.use; - logger.info("openhab url: {}, file: {}", openhabUrl, file); + Root root; + OpenHAB2Model model; + switch (settings.initModelWith) { + case openhab: + OpenHab2Importer importer = new OpenHab2Importer(); + try { + model = importer.importFrom(new URL(settings.openhab.url)); + root = model.getRoot(); + } catch (MalformedURLException e) { + logger.error("Could not parse URL {}", settings.openhab.url); + logger.catching(e); + System.exit(1); + return; + } + logger.info("Imported model {}", model.description()); + break; + case load: + default: + try { + root = ParserUtils.load(settings.load.realURL()); + model = root.getOpenHAB2Model(); + } catch (IOException | Parser.Exception e) { + logger.error("Problems parsing the given file {}", settings.load.file); + logger.catching(e); + System.exit(1); + return; + } + } - if (openhabUrl != null && file != null) { - logger.error("Both openhab url and file were set, can't process both. " + - "OpenHAB synchronization is done if MQTT url is set in the processed file. Exiting."); - System.exit(1); + // initialize backup learner + Learner learner = new Learner(); + + // initialize activity recognition + MachineLearningRoot machineLearningRoot = root.getMachineLearningRoot(); + if (settings.activity.dummy) { + logger.info("Using dummy activity recognition, ignoring other settings for this"); + machineLearningRoot.setActivityRecognition(DummyMachineLearningModel.createDefault()); + } else { + MachineLearningImpl handler = new MachineLearningImpl(learner, MachineLearningImpl.GOAL_ACTIVITY_PHONE_AND_WATCH); + handler.setKnowledgeBaseRoot(root); + logger.info("Reading activity recognition from csv file {}", settings.activity.file); + handler.initActivities(settings.activity.realURL().getFile()); + ExternalMachineLearningModel machineLearningModel = new ExternalMachineLearningModel(); + machineLearningModel.setEncoder(handler); + machineLearningModel.setDecoder(handler); + root.getMachineLearningRoot().setActivityRecognition(machineLearningModel); + + //Begin the Integration + Item item1=model.resolveItem("m_accel_x").get(); + Item item2=model.resolveItem("m_accel_y").get(); + Item item3=model.resolveItem("m_accel_z").get(); + Item item4=model.resolveItem("m_rotation_x").get(); + Item item5=model.resolveItem("m_rotation_y").get(); + Item item6=model.resolveItem("m_rotation_z").get(); + Item item7=model.resolveItem("w_accel_x").get(); + Item item8=model.resolveItem("w_accel_y").get(); + Item item9=model.resolveItem("w_accel_z").get(); + Item item10=model.resolveItem("w_rotation_x").get(); + Item item11=model.resolveItem("w_rotation_y").get(); + Item item12=model.resolveItem("w_rotation_z").get(); + //0.2717419,8.698134,4.471172,0.043741,0.515962,0.854318,1.8818425,4.9320555,8.145074,0.2374878,-0.032836914,0.3381958,working + item1.setStateFromString("0.2717419"); + item2.setStateFromString("8.698134"); + item3.setStateFromString("4.471172"); + item4.setStateFromString("0.043741"); + item5.setStateFromString("0.515962"); + item6.setStateFromString("0.854318"); + item7.setStateFromString("1.8818425"); + item8.setStateFromString("4.9320555"); + item9.setStateFromString("8.145074"); + item10.setStateFromString("0.2374878"); + item11.setStateFromString("-0.032836914"); + item12.setStateFromString("0.3381958"); + + ArrayList<Item> newData=new ArrayList<>(); + newData.add(item1); + newData.add(item2); + newData.add(item3); + newData.add(item4); + newData.add(item5); + newData.add(item6); + newData.add(item7); + newData.add(item8); + newData.add(item9); + newData.add(item10); + newData.add(item11); + newData.add(item12); + handler.newData(newData); + List<ItemPreference> preference=handler.classify().getPreferences(); + for(ItemPreference preference1 : preference){ + preference1.apply(); + } + } + + // initialize preference learning + if (settings.preference.dummy) { + logger.info("Using dummy preference learning, ignoring other settings for this"); + machineLearningRoot.setPreferenceLearning(DummyMachineLearningModel.createDefault()); + } else { + logger.info("Reading preference learning from csv file {}", settings.preference.file); + MachineLearningImpl handler = new MachineLearningImpl(learner, MachineLearningImpl.GOAL_PREFERENCE_BRIGHTNESS_IRIS); + handler.setKnowledgeBaseRoot(root); + handler.initPreferences(settings.preference.realURL().getFile()); + ExternalMachineLearningModel machineLearningModel = new ExternalMachineLearningModel(); + machineLearningModel.setEncoder(handler); + machineLearningModel.setDecoder(handler); + root.getMachineLearningRoot().setPreferenceLearning(machineLearningModel); + //working,medium,240,70 + Item activity_item = model.resolveItem("activity").get(); + String activity=activity_item.getStateAsString(); + activity_item.setStateFromString(activity); + Item brightness_item = model.resolveItem("w_brightness").get(); + brightness_item.setStateFromString("medium"); + ArrayList<Item> newData1 = new ArrayList<>(); + newData1.add(activity_item); + newData1.add(brightness_item); + handler.newData(newData1); + List<ItemPreference> preference=handler.classify().getPreferences(); + for(ItemPreference preference1 : preference){ + preference1.apply(); + } } - if (openhabUrl == null && file == null) { - logger.error("Neither URL for openHAB import nor a file was given, can't start uninitialized. Exiting."); + + machineLearningRoot.getPreferenceLearning().connectItems(settings.preference.items); + if (!machineLearningRoot.getActivityRecognition().check()) { + logger.fatal("Invalid activity recognition!"); System.exit(1); } - Root model; - if (openhabUrl != null) { - OpenHab2Importer importer = new OpenHab2Importer(); - try { - model = importer.importFrom(new URL(openhabUrl)); - } catch (MalformedURLException e) { - logger.error("Could not parse URL {}", openhabUrl); - logger.catching(e); - System.exit(1); - return; - } - logger.info("Imported model {}", model.description()); - } else { // file != null - try { - model = ParserUtils.load(file); - } catch (IOException | Parser.Exception e) { - logger.error("Problems parsing the given file {}", file); - logger.catching(e); - System.exit(1); - return; - } + if (!machineLearningRoot.getPreferenceLearning().check()) { + logger.fatal("Invalid preference learning!"); + System.exit(1); } + Lock lock = new ReentrantLock(); + Condition quitCondition = lock.newCondition(); + Analyze analyze = null; if (settings.useMAPE) { // configure and start mape loop @@ -103,85 +228,80 @@ public class EraserStarter { analyze.setPlan(plan); plan.setExecute(execute); - analyze.setKnowledgeBase(model); - plan.setKnowledgeBase(model); - execute.setKnowledgeBase(model); + analyze.setKnowledgeBase(root); + plan.setKnowledgeBase(root); + execute.setKnowledgeBase(root); analyze.startAsThread(1, TimeUnit.SECONDS); - if (!startRest) { - // alternative exit condition - System.out.println("Hit [Enter] to exit"); - try { - System.in.read(); - } catch (IOException e) { - e.printStackTrace(); - } - - System.out.println("Stopping..."); - analyze.stop(); - } } else { logger.info("No MAPE loop this time"); } + if (settings.mqttUpdate) { + logger.info("Starting MQTT updater"); + Thread t = new Thread(() -> { + try (MQTTUpdater updater = new MQTTUpdater(root)) { + updater.start(); + updater.waitUntilReady(5, TimeUnit.SECONDS); + lock.lock(); + quitCondition.await(); + } catch (IOException | InterruptedException e) { + logger.catching(e); + } finally { + lock.unlock(); + } + logger.info("MQTT update stopped"); + }, "MQTT-Updater"); + t.setDaemon(true); + t.start(); + } + if (startRest) { // start REST-API in new thread logger.info("Starting REST server"); -// Thread t = new Thread(new ThreadGroup("REST-API"), () -> SpringApplication.run(Application.class, new String[0])); -// Thread t = new Thread(new ThreadGroup("REST-API"), () -> Application.main(new String[0])); - Lock lock = new ReentrantLock(); - Condition quitCondition = lock.newCondition(); - Thread t = new Thread(new ThreadGroup("REST-API"), - () -> Application.start(settings.rest.port, model, settings.rest.createDummyMLData, lock, quitCondition)); + Thread t = new Thread( + () -> Application.start(settings.rest.port, root, settings.rest.createDummyMLData, lock, quitCondition), + "REST-API"); t.setDaemon(true); t.start(); - logger.info("Waiting until request is send to '/exit'"); + logger.info("Waiting until POST request is send to 'http://localhost:{}/system/exit'", settings.rest.port); try { lock.lock(); - quitCondition.await(); + if (t.isAlive()) { + quitCondition.await(); + } } catch (InterruptedException e) { logger.warn("Waiting was interrupted"); } finally { lock.unlock(); } - if (analyze != null) { - analyze.stop(); - } } else { logger.info("No REST server this time"); + System.out.println("Hit [Enter] to exit"); + try { + System.in.read(); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Stopping..."); + try { + lock.lock(); + quitCondition.signalAll(); + } finally { + lock.unlock(); + } + } + if (analyze != null) { + analyze.stop(); + } + InfluxAdapter influxAdapter = root.getInfluxRoot().influxAdapter(); + if (influxAdapter != null) { + try { + influxAdapter.close(); + } catch (Exception e) { + logger.catching(e); + } } logger.info("I'm done here."); } - - /* - ** Using argument parsing ** - - ArgumentParser parser = ArgumentParsers.newFor("Eraser starter").build() - .defaultHelp(true) - .description("This Starter combines and starts all modules. This includes:\n" + - " - Knowledge-Base in eraser-base\n" + - " - Feedback loop in feedbackloop.{analyze,plan,execute}\n" + - " - REST-API in eraser-rest"); - parser.addArgument("-o", "--openhab") - .metavar("URL") - .help("Initialize with openHAB at the given URL"); - parser.addArgument("--no-mape") - .action(Arguments.storeTrue()) - .setDefault(false) - .dest(NO_MAPE) - .help("Disable the feedback loop"); - parser.addArgument("--no-rest") - .action(Arguments.storeTrue()) - .setDefault(false) - .dest(NO_REST) - .help("Disable the REST-API"); - parser.addArgument("-f", "--file") - .help("*.eraser file to initialize knowledge base"); - Namespace ns = parser.parseArgsOrFail(args); - - String openhabUrl = ns.getString("openhab"); - String file = ns.getString("file"); - boolean initializeMape = !ns.getBoolean(NO_MAPE); - boolean startRest = !ns.getBoolean(NO_REST); - */ } diff --git a/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/Setting.java b/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/Setting.java index f4a1d1538da6b32cca5f889b728bf8847365c567..09af126b0a66f54aa2b6c88fb04bda9cb7218cb3 100644 --- a/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/Setting.java +++ b/eraser.starter/src/main/java/de/tudresden/inf/st/eraser/starter/Setting.java @@ -1,18 +1,86 @@ package de.tudresden.inf.st.eraser.starter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.List; + /** * Setting bean. * * @author rschoene - Initial contribution */ +@SuppressWarnings("WeakerAccess") class Setting { + private final static Logger logger = LogManager.getLogger(Setting.class); public class Rest { + /** Start the REST server. Default: true. */ public boolean use = true; + /** Port of the REST server. Default: 4567. */ public int port = 4567; + /** Add some dummy data for activities and events. Only effective when using the REST server. Default: false. */ public boolean createDummyMLData = false; } + public class FileContainer { + public String file; + /** Whether the file is external (not shipped with the JAR). Default: false. */ + public boolean external = false; + /** Get the URL to the {@link #file} */ + URL realURL() { + if (external) { + try { + return Paths.get(file).toUri().normalize().toURL(); + } catch (MalformedURLException e) { + logger.catching(e); + return null; + } + } else { + return Setting.class.getClassLoader().getResource(file); + } + } + } + public class MLContainer extends FileContainer { + /** Use dummy model in which the current activity is directly editable. Default: false. */ + public boolean dummy = false; + /** Model id. Default: 1.*/ + public int id = 1; + /** Items to connect to inputs */ + public List<String> items; + /** Item to change with classification result */ + public String affectedItem; + } + public class OpenHabContainer { + /** The URL from which to import and at which openHAB is running */ + public String url; + /** The metadata namespace for items */ + public String metadataNamespace; + } + public enum InitModelWith { + /** Load the initial model from a file */ + load, + /** Load the initial model by importing from openHAB */ + openhab + } public Rest rest; + /** Start the feedback loop. Default: true. */ public boolean useMAPE = true; - public String file; - public String openHabUrl; + /** Initialize the knowledge base with a file. + OpenHAB synchronization is done if MQTT url is set in the processed file. + <br> + child "file": File to read in. Expected format = eraser + */ + public FileContainer load; + /** Model for activity recognition. If dummy is true, then the file parameter is ignored. */ + public MLContainer activity; + /** Model for preference learning. If dummy is true, then the file parameter is ignored. */ + public MLContainer preference; + /** Initialize the knowledge base by importing data from openHAB. */ + public OpenHabContainer openhab; + /** Get updates from openHAB into the knowledge base. Default: true. */ + public boolean mqttUpdate = true; + /** Method to initialize model. Possible values: "load", "openhab". Default: "load". */ + public InitModelWith initModelWith = InitModelWith.load; } diff --git a/eraser.starter/src/main/resources/log4j2.xml b/eraser.starter/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..900e8c305d176e82d8f8a3c6beb1e96acfe186a8 100644 --- a/eraser.starter/src/main/resources/log4j2.xml +++ b/eraser.starter/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> @@ -18,5 +18,9 @@ <AppenderRef ref="Console"/> <AppenderRef ref="RollingFile"/> </Root> + <Logger name="org.eclipse.jetty" level="info" additivity="false"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Logger> </Loggers> </Configuration> diff --git a/eraser.starter/src/main/resources/preference.eg b/eraser.starter/src/main/resources/preference.eg new file mode 100644 index 0000000000000000000000000000000000000000..9c5e508c6466ebacdce5ea905bb60fd23ed499bd --- /dev/null +++ b/eraser.starter/src/main/resources/preference.eg @@ -0,0 +1,24 @@ +encog,BasicNetwork,java,3.4.0,1,1548842414446 +[BASIC] +[BASIC:PARAMS] +[BASIC:NETWORK] +beginTraining=0 +connectionLimit=0 +contextTargetOffset=0,0,0 +contextTargetSize=0,0,0 +endTraining=2 +hasContext=f +inputCount=4 +layerCounts=4,8,5 +layerFeedCounts=4,7,4 +layerContextCount=0,0,0 +layerIndex=0,4,12 +output=0.5121833792,-0.9601322536,-0.8758172535,-0.9979697503,0.9622664202,-0.9999734797,0.1592770336,0.799331477,-0.4541506621,0.9665504755,0.9965543485,1,0,0.9333333333,-0.2727272727,0.3898305085,1 +outputCount=4 +weightIndex=0,32,67 +weights=-1.0786840029,-2.2924792202,-1.9930859783,1.7958491576,0.1959961268,-2.0543645855,0.0362033078,0.2318024444,0.6379648215,0.6228614294,0.6327962466,1.4938586483,2.7154918438,-0.6821478373,-0.5520417752,-0.790812791,-0.6920871265,1.9974666857,2.3298834154,0.4485384863,-2.6899092835,0.031268515,-0.5795866919,-0.0980217125,-0.484143335,0.1511539927,0.420087673,-2.5050298291,0.2594955046,-1.095053346,1.3237034623,-1.0362683252,-1.4462851652,0.3753510899,-1.601258201,2.069584469,0.3818308747,1.9103650822,-1.0462844546,17.7814469175,0.6795954934,-0.0542764038,0.9582859267,0.3301209469,0.1352880767,0.0400876451,-0.1261986993,0.5055884018,0.6447792243,2.3674963793,1.1415287221,0.6956421989,0.3954445374,-0.0968051838,-5.3921438906,-1.0800675361,-1.4491070517,0.299983963,0.4432921269,-0.1229873384,0.3763334954,1.4430117456,2.3128771304,2.6904772398,-0.2334282302,2.656365234,-0.429273975 +biasActivation=0,1,1 +[BASIC:ACTIVATION] +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationLinear" diff --git a/eraser.starter/src/main/resources/starter.eraser b/eraser.starter/src/main/resources/starter.eraser new file mode 100644 index 0000000000000000000000000000000000000000..a8dc0c5731c9f5ce106c55aa4d2a87fc8f85df4f --- /dev/null +++ b/eraser.starter/src/main/resources/starter.eraser @@ -0,0 +1,40 @@ +Color Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item"; +Number Item: id="datetime_month" label="Month" state="1" topic="datetime_month"; +Number Item: id="datetime_day" label="Day" state="31" topic="datetime_day"; +Number Item: id="datetime_hour" label="Hour" state="13" topic="datetime_hour"; +Number Item: id="datetime_minute" label="Minute" state="37" topic="datetime_minute"; +Number Item: id="bias" label="bias item" state="1"; + + +String Item: id="m_accel_x" label="" state="0.2717419" topic="m_accel_x"; +String Item: id="m_accel_y" label="" state="8.698134" topic="m_accel_y"; +String Item: id="m_accel_z" label="" state="4.471172" topic="m_accel_z"; +String Item: id="m_rotation_x" label="" state="0.043741" topic="m_rotation_x"; +String Item: id="m_rotation_y" label="" state="0.515962" topic="m_rotation_y"; +String Item: id="m_rotation_z" label="" state="0.854318" topic="m_rotation_z"; +String Item: id="w_accel_x" label="" state="1.8818425" topic="w_accel_x"; +String Item: id="w_accel_y" label="" state="4.9320555" topic="w_accel_y"; +String Item: id="w_accel_z" label="" state="8.145074" topic="w_accel_z"; +String Item: id="w_rotation_x" label="" state="0.2374878" topic="w_rotation_x"; +String Item: id="w_rotation_y" label="" state="-0.032836914" topic="w_rotation_y"; +String Item: id="w_rotation_z" label="" state="0.3381958" topic="w_rotation_y"; +String Item: id="w_brightness" label="" state="bright" topic="w_brightness"; +//String Item: id="activity" label="" state="lying" topic="activity"; +Activity Item: id="activity" topic="activity"; + +Group: id="Lights" items=["iris1_item"]; +Group: id="Datetime" items=["datetime_month", "datetime_day", "datetime_hour", "datetime_minute"]; + +Mqtt: incoming="oh2/out/" outgoing="oh2/in/" host="localhost:1883" ; +//Mqtt: incoming="oh2/out/" outgoing="oh2/in/" host="192.168.1.250" ; + +Influx: host="172.22.1.152" ; +//"working", "walking", "dancing", "lying", "getting up", "reading" +ML: activities={ + 0: "working", + 1: "walking", + 2: "dancing", + 3: "lying", + 4: "getting up", + 5: "reading" +} ; diff --git a/eraser.starter/starter-setting.yaml b/eraser.starter/starter-setting.yaml index 0f2b6b8175e0518d2f66feca543e117383765d0a..e8f438cf3eeb22d71d44c675e7328e61ba612562 100644 --- a/eraser.starter/starter-setting.yaml +++ b/eraser.starter/starter-setting.yaml @@ -1,18 +1,64 @@ +# Settings for Eraser. Files are relative to the "resources" directory of this module. + # Start the feedback loop. Default: true. useMAPE: true rest: # Start the REST server. Default: true. use: true - # Port of the REST server. Default: 4567. port: 4567 - # Add some dummy data for activities and events. Only effective when using the REST server. Default: false. - createDummyMLData: true + createDummyMLData: false + +# Initialize the knowledge base with a file. +# OpenHAB synchronization is done if MQTT url is set in the processed file +load: + # File to read in. Expected format = eraser +# # Option 1: Use built-in file +# file: starter.eraser +# external: false + # Option 2: Use external file + file: src/main/resources/starter.eraser + external: true + +# Model for activity recognition. If dummy is true, then the file parameter is ignored. +activity: + # File to read in. Expected format = csv /Users/boqi/eraser/datasets + file: ../datasets/backup/activity_data.csv + external: true + # Use dummy model in which the current activity is directly editable. Default: false. + dummy: false + # Model id. Default: 1. + id: 1 + # Item to change with classification result + affectedItem: activity + +# Model for preference learning. If dummy is true, then the file parameter is ignored. +preference: + # File to read in. Expected format = csv + file: ../datasets/backup/preference_data.csv + external: true + # Use dummy model in which the current activity is directly editable. Default: false. + dummy: false + # Model id. Default: 1. + id: 1 + # Items to connect to inputs + items: + - activity + - w_brightness + # Item to change with classification result + affectedItem: iris1_item + +# Initialize the knowledge base by importing data from openHAB. +openhab: + # The URL from which to import and at which openHAB is running + url: "127.0.0.1:8080" + # The metadata namespace for items + metadataNamespace: fuenf -# Initialize the knowledge base with a file. No default. -file: /data/git/eraser/eraser.starter/src/main/resources/skywriter-hue.eraser +# Get updates from openHAB into the knowledge base. Default: true. +mqttUpdate: true -# Initialize the knowledge base by importing data from openHAB. No default. -#openHabUrl: "127.0.0.1:48080" +# Method to initialize model. Possible values: "load", "openhab". Default: "load". +initModelWith: load diff --git a/feedbackloop.analyze/.gitignore b/feedbackloop.analyze/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.analyze/.gitignore +++ b/feedbackloop.analyze/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.analyze/build.gradle b/feedbackloop.analyze/build.gradle index d14ed3e7311d54cdaec24165733da7a7caff7115..361f5c1429190208b8a70cbb889bf6c1cbd56993 100644 --- a/feedbackloop.analyze/build.gradle +++ b/feedbackloop.analyze/build.gradle @@ -1,28 +1,6 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' -apply plugin: 'application' - dependencies { compile project(':eraser-base') compile project(':feedbackloop.api') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' -} - -run { - mainClassName = 'de.tudresden.inf.st.eraser.feedbackloop.analyze.Main' - standardInput = System.in - if (project.hasProperty("appArgs")) { - args Eval.me(appArgs) - } } sourceSets { diff --git a/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/AnalyzeImpl.java b/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/AnalyzeImpl.java index e09c4e474c9861f126a098f3a49d1986cfc46d0d..5e9f8cfb5476ae25219cebd704e17e2e2b78cb9d 100644 --- a/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/AnalyzeImpl.java +++ b/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/AnalyzeImpl.java @@ -2,6 +2,7 @@ package de.tudresden.inf.st.eraser.feedbackloop.analyze; import de.tudresden.inf.st.eraser.feedbackloop.api.Analyze; import de.tudresden.inf.st.eraser.feedbackloop.api.Plan; +import de.tudresden.inf.st.eraser.jastadd.model.Activity; import de.tudresden.inf.st.eraser.jastadd.model.Root; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,11 +16,11 @@ public class AnalyzeImpl implements Analyze { private Root knowledgeBase; private Plan plan; - private String mostRecentActivity; + private Activity mostRecentActivity; private Logger logger = LogManager.getLogger(AnalyzeImpl.class); public AnalyzeImpl() { - this.mostRecentActivity = ""; + this.mostRecentActivity = null; } @Override @@ -39,13 +40,17 @@ public class AnalyzeImpl implements Analyze { @Override public void analyzeLatestChanges() { - // TODO change to "currentActivity" and track Activity objects, instead of Strings - String currentActivity = knowledgeBase.currentActivityName(); - if (!currentActivity.equals(mostRecentActivity)) { - // new! inform plan! - logger.debug("Found new activity '{}'", currentActivity); - mostRecentActivity = currentActivity; - informPlan(currentActivity); - } + knowledgeBase.currentActivity().ifPresent(activity -> { + if (!activity.equals(mostRecentActivity)) { + // new! inform plan! + logger.info("Found new activity '{}'", activity.getLabel()); + mostRecentActivity = activity; + try { + informPlan(activity); + } catch (Exception e) { + logger.catching(e); + } + } + }); } } diff --git a/feedbackloop.analyze/src/main/resources/log4j2.xml b/feedbackloop.analyze/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.analyze/src/main/resources/log4j2.xml +++ b/feedbackloop.analyze/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/feedbackloop.api/.gitignore b/feedbackloop.api/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.api/.gitignore +++ b/feedbackloop.api/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.api/build.gradle b/feedbackloop.api/build.gradle index 5264c7d2bc1f37c38a464fc021d9a0e9465a7998..ba194086c7c858a3435af764a8783f38231d2c69 100644 --- a/feedbackloop.api/build.gradle +++ b/feedbackloop.api/build.gradle @@ -1,19 +1,11 @@ -repositories { - mavenCentral() +plugins { + id 'io.franzbecker.gradle-lombok' version '3.0.0' } -sourceCompatibility = 1.8 - -apply plugin: 'java' - dependencies { compile project(':eraser-base') - compile project(':commons.color:') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile project(':commons.color') + compile group: 'org.encog', name: 'encog-core', version: '3.4' } sourceSets { diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Analyze.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Analyze.java index bdc70275aeea34e8dae963bdd23ac840c651fef9..d590f1e45f98f2830e0dc645c79c563fc13737d3 100644 --- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Analyze.java +++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Analyze.java @@ -1,5 +1,6 @@ package de.tudresden.inf.st.eraser.feedbackloop.api; +import de.tudresden.inf.st.eraser.jastadd.model.Activity; import de.tudresden.inf.st.eraser.jastadd.model.Root; import java.util.concurrent.Executors; @@ -31,7 +32,7 @@ public interface Analyze { executorService.shutdownNow(); } - default void informPlan(String activity) { + default void informPlan(Activity activity) { getPlan().planToMatchPreferences(activity); } } diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/EncogModel.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/EncogModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c0bc0336ea49e810ac5930c30600e271e65c6861 --- /dev/null +++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/EncogModel.java @@ -0,0 +1,44 @@ +package de.tudresden.inf.st.eraser.feedbackloop.api; + +import lombok.Getter; +import lombok.Setter; +import org.encog.neural.networks.layers.Layer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * + * This class represents an object that contains all information of a trained neural network. + * + * For now: weights and the {@link Layer}s + * + * @author Bierzyns - initial contribution + * */ +@Getter +@Setter +public class EncogModel { + /** + * todo + */ + private String modelType; + private List<Double> weights; + private List<Layer> layers; + + public EncogModel(String model) { + modelType = model; + } + + public Layer getInputLayer() { + return Objects.requireNonNull(layers, "Layers not set yet").get(0); + } + + public Layer getOutputLayer() { + return Objects.requireNonNull(layers, "Layers not set yet").get(layers.size() - 1); + } + + public List<Layer> getHiddenLayers() { + return Objects.requireNonNull(layers, "Layers not set yet").subList(1, layers.size() - 1); + } +} diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java index dd923208037d76ec7e78d8b1652951fbec7bef50..c7ecb57d535b832d59d76d9840813a6d8a72ca2d 100644 --- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java +++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java @@ -1,6 +1,5 @@ package de.tudresden.inf.st.eraser.feedbackloop.api; -import de.tudresden.inf.st.eraser.commons.color.ColorUtils; import de.tudresden.inf.st.eraser.commons.color.ColorUtils.RGBvalues; import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference; import de.tudresden.inf.st.eraser.jastadd.model.Root; @@ -16,9 +15,22 @@ import java.util.Map; */ public interface Execute { + /** + * Setter for the knowledge base to use + * @param knowledgeBase the knowledge base to use + */ void setKnowledgeBase(Root knowledgeBase); + /** + * <b>Deprecated</b>: Use {@link #updateItems(List)} instead. + * @param brightnessAndRgbForItems Map, keys are item names, values are RGB and brightness values + */ + @Deprecated void updateItems(Map<String, Tuple<Integer, RGBvalues>> brightnessAndRgbForItems); + /** + * Updates items according to given preferences + * @param preferences tuples containing item and its new HSB value + */ void updateItems(List<ItemPreference> preferences); } diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java index 07855cd001fdcaaef3927c901ac9bab9bbe2007a..a08a6fc2a444b4a30fd4f234acfd4449c075d444 100644 --- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java +++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java @@ -1,14 +1,18 @@ package de.tudresden.inf.st.eraser.feedbackloop.api; -import java.util.ArrayList; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +import org.encog.util.arrayutil.NormalizedField; -import de.tudresden.inf.st.eraser.feedbackloop.api.model.Model; import de.tudresden.inf.st.eraser.jastadd.model.Root; /** * - * Learner which handles the training and retraining of algorithms - * and models used for activity recognition, preference learning and data pre-processing. + * Learner which handles the training and retraining of neural networks + * and models used for activity recognition and preference learning. * * @author Bierzyns - initial contribution * @@ -18,46 +22,114 @@ public interface Learner { void setKnowledgeBase(Root knowledgeBase); /** - * Method for loading data set, which used for initial training. This method exists only for development purposes. + * Method for loading data set, which used for initial training. This method exists only for development purposes. The csvFolderPath + * variable should be set to ensure that the data is read from the correct place. * - * @param dataSetName - name of data set that is loaded from the data set folder e.g. data1.arff (Weka) + * @param dataSetName - name of data set that is loaded from the data set folder e.g. data1.csv + * @param targetColumns - number of the columns that contain label of the corresponding csv data set * @param modelID - ID of the model that will be trained with this data set * @return true - data set loading was successful * */ - boolean loadDataSet(String dataSetName, int modelID); + boolean loadDataSet(String dataSetName, List<Integer> targetColumns, int modelID); + + /** + * Method for loading a neural network from a file. + * Please note that the normalizer are note loaded file , because it is assumed that the mins and maxes are saved anyway in the meta data of the data sets or items. + * + * @param file file to load the model from + * @param modelID - ID of the BasicNetwork. + * @param inputMaxes - list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins - list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes - list that contains max values of all output columns (results) e.g. brigthness 100 for preference learning + * @param targetMins - list that contains min values of all output columns (results) e.g. brigthness 0 for preference learning + * @return true - model loading was successful + * */ + boolean loadModelFromFile(File file, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins); + + /** + * Method for loading a neural network from an input stream. + * Please note that the normalizer are note loaded file , because it is assumed that the mins and maxes are saved anyway in the meta data of the data sets or items. + * + * @param input stream to load the model from + * @param modelID - ID of the BasicNetwork. + * @param inputMaxes - list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins - list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes - list that contains max values of all output columns (results) e.g. brigthness 100 for preference learning + * @param targetMins - list that contains min values of all output columns (results) e.g. brigthness 0 for preference learning + * @return true - model loading was successful + * */ + boolean loadModelFromFile(InputStream input, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins); /** * Method for the initial training of algorithms and models. That uses external data set for training. * - * @param modelID - ID of model that will be trained + * @param inputCount - number of neurons in the input layer [here to simplify code reading] + * @param outputCount - number of neurons in the output layer [here to simplify code reading] + * @param hiddenCount -number of hidden layers in the network + * @param hiddenNeuronCount - number of neurons in the hidden layers for now + * @param modelID - ID of the BasicNetwork. + * @param inputMaxes - list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins - list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes - list that contains max values of all output columns (results) e.g. brigthness 100 for preference learning + * @param targetMins - list that contains min values of all output columns (results) e.g. brigthness 0 for preference learning + * * @return true - training of the model was successful started * */ - boolean train(int modelID); + boolean train(int inputCount, int outputCount, int hiddenCount, int hiddenNeuronCount, int modelID, + List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins); /** - * Method for the initial training of algorithms and models. That uses data set provided by Knowledge Base. Format of Data? + * Method for the initial training of algorithms and models. That uses data set provided by Knowledge Base. * - * @param set - data set on which the training is based on - * @param modelID - ID of model that will be trained + * @param data - data set on which the training is based on + * @param inputCount - number of neurons in the input layer [here to simplify code reading] + * @param outputCount - number of neurons in the output layer [here to simplify code reading] + * @param hiddenCount - number of hidden layers in the network + * @param hiddenNeuronCount - number of neurons in the hidden layers for now + * @param modelID - ID of the BasicNetwork. + * @param inputMaxes - list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins - list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes - list that contains max values of all output columns (results) e.g. brigthness 100 for preference learning + * @param targetMins - list that contains min values of all output columns (results) e.g. brigthness 0 for preference learning * @return true - training of the model was successful started * */ - boolean train(ArrayList<Integer> set, int modelID); + boolean train(double[][] data, int inputCount, int outputCount, int hiddenCount, int hiddenNeuronCount, int modelID, + List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins, List<Integer> targetColumns); /** - * Method for adapting existing models. Format of new data? + * Method for adapting existing models. * - * @param set - data set on which the retraining is based on + * @param data - data set on which the retraining is based on + * @param targetColumns - number of the columns thta contain labels/target * @param modelID - ID of the model that will be retrained * @return true - retraining of model was started successfully * */ - boolean reTrain(ArrayList<Integer> set, int modelID); + boolean reTrain(double[][] data, List<Integer> targetColumns, int modelID); /** * Method for getting the information about an trained model. * - * @param modelID - ID of the model of which information are requested from - * @return Model - Object that contains the information of the requested model + * @param modelID - ID of the model of which information is requested from + * @return Model - Object that contains the information of the requested model * */ - Model getTrainedModel(int modelID); + EncogModel getTrainedModel(int modelID); + + @Deprecated + EncogModel getTrainedModel(URL url, int modelID); + /** + * + * Method for getting normalizer of a model for a specific column/input. + * + * @param modelID - ID of the model of which normalizer is requested from + * @param columnNr - number of columns the normalizer can be used for + * + * @return {@link NormalizedField} - the normalizer + * */ + NormalizedField getNormalizerInput(int modelID, int columnNr); + } diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Plan.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Plan.java index f0c48bad8609f50833b886215d26d62fe4c4f7f8..8c237d06d64684d6386e1bc8de5a73146d7a1485 100644 --- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Plan.java +++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Plan.java @@ -1,5 +1,6 @@ package de.tudresden.inf.st.eraser.feedbackloop.api; +import de.tudresden.inf.st.eraser.jastadd.model.Activity; import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference; import de.tudresden.inf.st.eraser.jastadd.model.Root; @@ -18,7 +19,7 @@ public interface Plan { Execute getExecute(); - void planToMatchPreferences(String activity); + void planToMatchPreferences(Activity activity); default void informExecute(List<ItemPreference> preferences) { getExecute().updateItems(preferences); diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/model/Model.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/model/Model.java deleted file mode 100644 index 3a51f014248b5aaf1507877bd9d10f978915f221..0000000000000000000000000000000000000000 --- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/model/Model.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.tudresden.inf.st.eraser.feedbackloop.api.model; - -/** - * - * This class represents an object that contains all information of a trained model. - * - * @author Bierzyns - initial contribution - * */ -public class Model { - - private String modelType; - - public Model(String model) { - modelType=model; - } - - - /** - * Getter for model type. - * - * @return modelType - Possible results: NN or DT - * */ - String getModelType(){ - return modelType; - }; -} diff --git a/feedbackloop.api/src/main/resources/log4j2.xml b/feedbackloop.api/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.api/src/main/resources/log4j2.xml +++ b/feedbackloop.api/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/feedbackloop.execute/.gitignore b/feedbackloop.execute/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.execute/.gitignore +++ b/feedbackloop.execute/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.execute/build.gradle b/feedbackloop.execute/build.gradle index 4365ddbc914e60874556d7bfa5cfcf9531c130a0..361f5c1429190208b8a70cbb889bf6c1cbd56993 100644 --- a/feedbackloop.execute/build.gradle +++ b/feedbackloop.execute/build.gradle @@ -1,28 +1,6 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' -apply plugin: 'application' - dependencies { compile project(':eraser-base') compile project(':feedbackloop.api') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' -} - -run { - mainClassName = 'de.tudresden.inf.st.eraser.feedbackloop.execute.Main' - standardInput = System.in - if (project.hasProperty("appArgs")) { - args Eval.me(appArgs) - } } sourceSets { diff --git a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java b/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java index 1482639bf3b29d6785a7faa3ba167b13cdb6992e..80d2346312445914306e6c224d1f9de1b766cf6d 100644 --- a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java +++ b/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java @@ -9,10 +9,7 @@ import de.tudresden.inf.st.eraser.util.Tuple; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; /** @@ -32,6 +29,7 @@ public class ExecuteImpl implements Execute { @Override public void updateItems(Map<String, Tuple<Integer, RGBvalues>> brightnessAndRgbForItems) { + List<ItemPreference> preferences = new ArrayList<>(); for (Map.Entry<String, Tuple<Integer, RGBvalues>> entry : brightnessAndRgbForItems.entrySet()) { String itemId = entry.getKey(); resolveOrLogError(itemId, item -> { @@ -49,34 +47,21 @@ public class ExecuteImpl implements Execute { hsb = HSBvalues255.of(0, 100, brightness); } hsb.ensureBounds(); - if (item instanceof ColorItem) { - ((ColorItem) item).setState(TupleHSB.of(hsb.hue, hsb.saturation, hsb.brightness)); - } else if (item instanceof ItemWithDoubleState) { - ((ItemWithDoubleState) item).setState(brightness); - } else { - logger.warn("Can not set {} for item {}", hsb, item); - } + preferences.add(new ItemPreferenceColor(item, TupleHSB.of(hsb.hue, hsb.saturation, hsb.brightness))); }); } + updateItems(preferences); } @Override public void updateItems(List<ItemPreference> preferences) { for (ItemPreference preference : preferences) { - Item item = preference.getItem(); - TupleHSB preferredHSB = preference.getPreferredHSB(); - if (item instanceof ColorItem) { - ((ColorItem) item).setState(preferredHSB); - } else if (item instanceof ItemWithDoubleState) { - ((ItemWithDoubleState) item).setState(preferredHSB.brightness); - } else { - logger.warn("Can not set {} for item {}", preferredHSB, item); - } + preference.apply(); } } private void resolveOrLogError(String itemId, Consumer<? super Item> consumer) { - Optional<Item> optionalItem = knowledgeBase.resolveItem(itemId); + Optional<Item> optionalItem = knowledgeBase.getOpenHAB2Model().resolveItem(itemId); if (!optionalItem.isPresent()) { logger.warn("Could not resolve '{}' as an item.", itemId); } diff --git a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/Main.java b/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/Main.java deleted file mode 100644 index a191317a8a4211a725640e9739cf2ce25b53739f..0000000000000000000000000000000000000000 --- a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/Main.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.tudresden.inf.st.eraser.feedbackloop.execute; - -import de.tudresden.inf.st.eraser.jastadd.model.*; - -public class Main { - - public static void main(String[] args) { - System.out.println("Hello World!"); - } -} diff --git a/feedbackloop.execute/src/main/resources/log4j2.xml b/feedbackloop.execute/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.execute/src/main/resources/log4j2.xml +++ b/feedbackloop.execute/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/feedbackloop.execute/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImplTest.java b/feedbackloop.execute/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f79ac72847b1c8c251c96a6d011014479f7115 --- /dev/null +++ b/feedbackloop.execute/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImplTest.java @@ -0,0 +1,129 @@ +package de.tudresden.inf.st.eraser.feedbackloop.execute; + +import de.tudresden.inf.st.eraser.feedbackloop.api.Execute; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.TestUtils; +import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; + +/** + * Test updating items within the Execute component. + * + * @author rschoene - Initial contribution + */ +public class ExecuteImplTest { + + private static final double DELTA = 0.001; + + @Test + public void testColorControlledByOneNumber() { + ModelAndItem mai = TestUtils.createModelAndItem(0); + OpenHAB2Model model = mai.model; + NumberItem numberItem = mai.item; + + ColorItem lamp = new ColorItem(); + lamp.setID("lamp"); + lamp.setState(TupleHSB.of(0, 0, 0), false); + lamp.enableSendState(); + TestUtils.getDefaultGroup(model).addItem(lamp); + + numberItem.addControlling(lamp); + + Execute execute = new ExecuteImpl(); + execute.setKnowledgeBase(model.getRoot()); + + Assert.assertEquals(0, numberItem.getState(), DELTA); + Assert.assertEquals(TupleHSB.of(0, 0, 0), lamp.getState()); + + ItemPreference preference = new ItemPreferenceColor(lamp, TupleHSB.of(1, 2, 3)); + execute.updateItems(Collections.singletonList(preference)); + + Assert.assertEquals(3, numberItem.getState(), DELTA); + Assert.assertEquals(TupleHSB.of(1, 2, 3), lamp.getState()); + } + + @Test + public void testColorControlledByOneBoolean() { + OpenHAB2Model model = TestUtils.createModelAndItem(0).model; + + ItemWithBooleanState button = new SwitchItem(); + button.setID("button"); + button.setState(false, false); + button.enableSendState(); + TestUtils.getDefaultGroup(model).addItem(button); + + ColorItem lamp = new ColorItem(); + lamp.setID("lamp"); + lamp.setState(TupleHSB.of(0, 0, 0), false); + lamp.enableSendState(); + TestUtils.getDefaultGroup(model).addItem(lamp); + + button.addControlling(lamp); + + Execute execute = new ExecuteImpl(); + execute.setKnowledgeBase(model.getRoot()); + + Assert.assertFalse(button.getState()); + Assert.assertEquals(TupleHSB.of(0, 0, 0), lamp.getState()); + + ItemPreference preference = new ItemPreferenceColor(lamp, TupleHSB.of(1, 2, 3)); + execute.updateItems(Collections.singletonList(preference)); + + Assert.assertTrue(button.getState()); + Assert.assertEquals(TupleHSB.of(1, 2, 3), lamp.getState()); + } + + @Test + public void testColorControlledByMany() { + ModelAndItem mai = TestUtils.createModelAndItem(0); + OpenHAB2Model model = mai.model; + NumberItem numberItem = mai.item; + + Group g = TestUtils.getDefaultGroup(model); + + StringItem stringItem = new StringItem(); + stringItem.setState("0", false); + g.addItem(stringItem); + + SwitchItem booleanItem = new SwitchItem(); + booleanItem.setState(false, false); + g.addItem(booleanItem); + + ColorItem colorItem = new ColorItem(); + colorItem.setState(TupleHSB.of(0, 0, 0), false); + g.addItem(colorItem); + + ColorItem lamp = new ColorItem(); + lamp.setID("lamp"); + lamp.setState(TupleHSB.of(0, 0, 0), false); + lamp.enableSendState(); + TestUtils.getDefaultGroup(model).addItem(lamp); + + lamp.addControlledBy(numberItem); + lamp.addControlledBy(stringItem); + lamp.addControlledBy(booleanItem); + lamp.addControlledBy(colorItem); + + Execute execute = new ExecuteImpl(); + execute.setKnowledgeBase(model.getRoot()); + + Assert.assertEquals(0, numberItem.getState(), DELTA); + Assert.assertEquals("0", stringItem.getState()); + Assert.assertFalse(booleanItem.getState()); + Assert.assertEquals(TupleHSB.of(0, 0, 0), colorItem.getState()); + Assert.assertEquals(TupleHSB.of(0, 0, 0), lamp.getState()); + + ItemPreference preference = new ItemPreferenceColor(lamp, TupleHSB.of(1, 2, 3)); + execute.updateItems(Collections.singletonList(preference)); + + Assert.assertEquals(3, numberItem.getState(), DELTA); + Assert.assertEquals("1,2,3", stringItem.getState()); + Assert.assertTrue(booleanItem.getState()); + Assert.assertEquals(TupleHSB.of(1, 2, 3), colorItem.getState()); + Assert.assertEquals(TupleHSB.of(1, 2, 3), lamp.getState()); + } + +} diff --git a/feedbackloop.learner/.gitignore b/feedbackloop.learner/.gitignore index 4a95481e611021403799e1b0a49ae9c4f18d2e2f..86db2fb7dd0bbe9d7ec2c79d624db271fcfd8e10 100644 --- a/feedbackloop.learner/.gitignore +++ b/feedbackloop.learner/.gitignore @@ -1,2 +1,3 @@ /bin/ /build/ +logs/ diff --git a/feedbackloop.learner/.settings/org.eclipse.buildship.core.prefs b/feedbackloop.learner/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index b1886adb46c085de842f1283c1a3c25151bfc988..0000000000000000000000000000000000000000 --- a/feedbackloop.learner/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/feedbackloop.learner/build.gradle b/feedbackloop.learner/build.gradle index f6e3fbeabed049106c8e5760001ee8b16fa08ec7..3ad295bf3c112d55e31fb6d185cef21ead1da66d 100644 --- a/feedbackloop.learner/build.gradle +++ b/feedbackloop.learner/build.gradle @@ -1,20 +1,9 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') compile project(':feedbackloop.api') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.encog', name: 'encog-core', version: '3.4' } run { diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Dataset.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Dataset.java new file mode 100644 index 0000000000000000000000000000000000000000..0b09cbd2c73a753fa2de5b74b8bbe509e8e75a36 --- /dev/null +++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Dataset.java @@ -0,0 +1,36 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner; + +import org.encog.util.csv.ReadCSV; + +import java.util.List; + +/** + * This class is an representation of a data set in csv format. + * The data is saved and made accessible by {@link ReadCSV} and the numbers of the target columns is saved as meta-data. + * + * @author Bierzyns - initial contribution + * + * */ +public class Dataset { + + private final ReadCSV csv; + private final List<Integer> targetColumns; + + public Dataset(ReadCSV csv, List<Integer> targetColumns) { + this.csv = csv; + this.targetColumns = targetColumns; + } + + public int getColumnCount() { + return csv.getColumnCount(); + } + + public List<Integer> getTargetColumns() { + return targetColumns; + } + + public ReadCSV getCsv() { + return csv; + } + +} diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerHelper.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..f23b58105b78ea6b84a3f11211e843eb28f2cf13 --- /dev/null +++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerHelper.java @@ -0,0 +1,150 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner; + +import de.tudresden.inf.st.eraser.feedbackloop.api.EncogModel; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import org.apache.commons.math3.stat.StatUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.encog.engine.network.activation.ActivationFunction; +import org.encog.engine.network.activation.ActivationLinear; +import org.encog.engine.network.activation.ActivationSigmoid; +import org.encog.engine.network.activation.ActivationTANH; +import org.encog.neural.networks.layers.Layer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Transformation of a {@link EncogModel} into a {@link NeuralNetworkRoot}. + * + * @author rschoene - Initial contribution + */ +public class LearnerHelper { + + private static final Logger logger = LogManager.getLogger(LearnerHelper.class); + + // Activation Functions + private static DoubleArrayDoubleFunction sigmoid = inputs -> Math.signum(Arrays.stream(inputs).sum()); + private static DoubleArrayDoubleFunction tanh = inputs -> Math.tanh(Arrays.stream(inputs).sum()); + private static DoubleArrayDoubleFunction function_one = inputs -> 1.0; + + public static NeuralNetworkRoot transform(EncogModel encogModel) { + NeuralNetworkRoot result = NeuralNetworkRoot.createEmpty(); + List<Double> weights = encogModel.getWeights(); + logger.debug("Got {} weights", weights.size()); + + List<List<Neuron>> allNeurons = new ArrayList<>(); + // inputs + Layer inputLayer = encogModel.getInputLayer(); + reportLayer("input", inputLayer); + List<Neuron> inputNeurons = new ArrayList<>(); + for (int i = 0; i < nonBiasNeuronCount(inputLayer); ++i) { + InputNeuron inputNeuron = new InputNeuron(); + result.addInputNeuron(inputNeuron); + inputNeurons.add(inputNeuron); + } + addBiasIfNeeded(inputLayer, result.getHiddenNeuronList(), inputNeurons); + allNeurons.add(inputNeurons); + + // hidden layer + List<Neuron> currentNeurons; + for (Layer hiddenLayer : encogModel.getHiddenLayers()) { + reportLayer("one hidden", hiddenLayer); + currentNeurons = new ArrayList<>(); + allNeurons.add(currentNeurons); + for (int i = 0; i < nonBiasNeuronCount(hiddenLayer); ++i) { + HiddenNeuron hiddenNeuron = new HiddenNeuron(); + setActivationFunction(hiddenNeuron, hiddenLayer.getActivationFunction()); + result.addHiddenNeuron(hiddenNeuron); + currentNeurons.add(hiddenNeuron); + } + addBiasIfNeeded(hiddenLayer, result.getHiddenNeuronList(), currentNeurons); + } + + // output layer + OutputLayer outputLayer = new OutputLayer(); + Layer modelOutputLayer = encogModel.getOutputLayer(); + reportLayer("output", modelOutputLayer); + List<Neuron> outputNeurons = new ArrayList<>(); + for (int i = 0; i < nonBiasNeuronCount(modelOutputLayer); ++i) { + OutputNeuron outputNeuron = new OutputNeuron(); + setActivationFunction(outputNeuron, modelOutputLayer.getActivationFunction()); + outputLayer.addOutputNeuron(outputNeuron); + outputNeurons.add(outputNeuron); + } + result.setOutputLayer(outputLayer); + allNeurons.add(outputNeurons); + logger.debug("Created a total of {} neurons", + allNeurons.stream() + .map(list -> Integer.toString(list.size())) + .collect(Collectors.joining("+"))); + + // set weights from back to front, and from top to bottom + int weightIndex = 0; + for (int layer = allNeurons.size() - 1; layer > 0; --layer) { + List<Neuron> rightList = allNeurons.get(layer); + List<Neuron> leftList = allNeurons.get(layer - 1); + for (int rightIndex = 0; rightIndex < rightList.size(); rightIndex++) { + for (int leftIndex = 0; leftIndex < leftList.size(); leftIndex++) { + if (rightList.get(rightIndex) instanceof BiasNeuron) { + continue; + } + leftList.get(leftIndex).connectTo(rightList.get(rightIndex), weights.get(weightIndex++)); + } + } + } + if (weightIndex != weights.size()) { + logger.error("No all weights used (only {} of {}). Loaded wrong model!", weightIndex, weights.size()); + } + + outputLayer.setCombinator(LearnerHelper::predictor); + logger.info("Created model with {} input, {} hidden and {} output neurons", + result.getNumInputNeuron(), result.getNumHiddenNeuron(), result.getOutputLayer().getNumOutputNeuron()); + return result; + } + + private static void addBiasIfNeeded(Layer layer, JastAddList<HiddenNeuron> neuronList, List<Neuron> localNeuronList) { + if (layer.hasBias()) { + BiasNeuron bias = new BiasNeuron(); + neuronList.add(bias); + localNeuronList.add(bias); + } + } + + private static int nonBiasNeuronCount(Layer layer) { + return layer.getNeuronCount() - (layer.hasBias() ? 1 : 0); + } + + private static void reportLayer(String name, Layer layer) { + logger.debug("{} layer has {} neurons {}", + name, layer.getNeuronCount(), layer.hasBias() ? "(including bias)" : ""); + } + + private static void setActivationFunction(HiddenNeuron neuron, ActivationFunction function) { + if (function instanceof ActivationTANH) { + neuron.setActivationFormula(tanh); + } else if (function instanceof ActivationLinear) { + neuron.setActivationFormula(function_one); + } else if (function instanceof ActivationSigmoid) { + neuron.setActivationFormula(sigmoid); + } else { + throw new IllegalArgumentException("Unknown activation function " + function.getClass().getName()); + } + } + + private static double predictor(double[] inputs) { + int index = 0; + double maxInput = StatUtils.max(inputs); + for (int i = 0; i < inputs.length; i++) { + if (inputs[i] == maxInput) { + index = i; + } + } + //outputs from learner + final double[] outputs = new double[]{2.0, 1.0, 3.0, 0.0}; + return outputs[index]; + } + +} diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java index a229d11eda814a21920c2b9fa40400fe59bb24d8..83edf6b7f195d9be6420369d4590955c42449735 100644 --- a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java +++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java @@ -1,52 +1,230 @@ package de.tudresden.inf.st.eraser.feedbackloop.learner; -import java.util.ArrayList; - +import de.tudresden.inf.st.eraser.feedbackloop.api.EncogModel; import de.tudresden.inf.st.eraser.feedbackloop.api.Learner; -import de.tudresden.inf.st.eraser.feedbackloop.api.model.Model; import de.tudresden.inf.st.eraser.jastadd.model.Root; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.encog.neural.flat.FlatNetwork; +import org.encog.neural.networks.BasicNetwork; +import org.encog.neural.networks.layers.BasicLayer; +import org.encog.neural.networks.layers.Layer; +import org.encog.util.arrayutil.NormalizedField; +import org.encog.util.csv.CSVFormat; +import org.encog.util.csv.ReadCSV; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; /** * Implementation of the Learner. * * @author Bierzyns - Initial contribution */ -public class LearnerImpl implements Learner{ - - private Root knowledgeBase; - - @Override - public void setKnowledgeBase(Root knowledgeBase) { - this.knowledgeBase = knowledgeBase; - } - - @Override - public boolean loadDataSet(String dataSetName, int modelID) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean train(int modelID) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean train(ArrayList<Integer> set, int modelID) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean reTrain(ArrayList<Integer> set, int modelID) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Model getTrainedModel(int modelID) { - return new Model("NN"); - } +public class LearnerImpl implements Learner { + + + private Root knowledgeBase; + private static final Logger logger = LogManager.getLogger(LearnerImpl.class); + private Path csvFolderPath = Paths.get("src", "main", "resources"); + private String modelFolderPath = "."; + private CSVFormat format = new CSVFormat('.', ','); + private Map<Integer, Dataset> datasets = new HashMap<>(); + private Map<Integer, Network> models = new HashMap<>(); + + + @Override + public void setKnowledgeBase(Root knowledgeBase) { + this.knowledgeBase = knowledgeBase; + } + + public void setCsvFolderPath(String csvFolderPath) { + this.csvFolderPath = Paths.get(csvFolderPath); + } + + public void setModelFolderPath(String modelFolderPath) { + this.modelFolderPath = modelFolderPath; + } + + @Override + public boolean loadDataSet(String dataSetName, List<Integer> targetColumns, int modelID) { + Path realDataSetPath = csvFolderPath.resolve(dataSetName); + logger.debug("Load data set from file {}", realDataSetPath); + try { + Dataset set = new Dataset(new ReadCSV(realDataSetPath.toFile().getAbsoluteFile(), false, format), targetColumns); + datasets.put(modelID, set); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + public boolean loadModelFromFile(File file, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + logger.debug("Load model from file {}", file); + models.put(modelID, new Network(file.getAbsolutePath(), modelID, inputMaxes, inputMins, targetMaxes, targetMins)); + return true; + } + + @Override + public boolean loadModelFromFile(InputStream input, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + logger.debug("Load model from input stream"); + models.put(modelID, new Network(input, modelID, inputMaxes, inputMins, targetMaxes, targetMins)); + return true; + } + + @Override + public boolean train(int inputCount, int outputCount, int hiddenCount, int hiddenNeuronCount, int modelID, + List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + // Method for the initial training of algorithms and models. That uses external data set for training. + + if (datasets.get(modelID) != null) { + Dataset set = datasets.get(modelID); + + ReadCSV csv = set.getCsv(); + + Network model = new Network(inputCount, outputCount, hiddenCount, hiddenNeuronCount, modelID, inputMaxes, + inputMins, targetMaxes, targetMins); + + ArrayList<Double> input = new ArrayList<>(); + ArrayList<Double> target = new ArrayList<>(); + + while (csv.next()) { + logger.debug("Train next csv row"); + for (int i = 0; i < csv.getColumnCount(); i++) { + int col_nr = i + 1; + if (set.getTargetColumns().contains(col_nr)) { + target.add(csv.getDouble(i)); + } else { + input.add(csv.getDouble(i)); + } + } + + model.train(input, target); + input.clear(); + target.clear(); + } + + models.put(modelID, model); + model.saveModel(modelFolderPath); + + return true; + } + return false; + } + + @Override + public boolean train(double[][] data, int inputCount, int outputCount, int hiddenCount, int hiddenNeuronCount, int modelID, + List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins, List<Integer> targetColumns) { + + Network model = new Network(inputCount, outputCount, hiddenCount, hiddenNeuronCount, modelID, inputMaxes, + inputMins, targetMaxes, targetMins); + + return reTrainModel(model, data, targetColumns, modelID); + } + + + @Override + public boolean reTrain(double[][] data, List<Integer> targetColumns, int modelID) { + + Network model = models.get(modelID); + + return reTrainModel(model, data, targetColumns, modelID); + } + + private boolean reTrainModel(Network model, double[][] data, List<Integer> targetColumns, int modelID) { + List<Double> input = new ArrayList<>(); + List<Double> target = new ArrayList<>(); + + for (int i = 0; i < data.length; i++) { + + for (int j = 0; j < data[0].length; j++) { + int col_nr = j + 1; + if (targetColumns.contains(col_nr)) { + target.add(data[i][j]); + } else { + input.add(data[i][j]); + } + + model.train(input, target); + input.clear(); + target.clear(); + } + + } + + models.put(modelID, model); + model.saveModel(modelFolderPath); + + return true; + } + + + @Override + public EncogModel getTrainedModel(int modelID) { + return fillModel(modelID); + } + + @Override + public EncogModel getTrainedModel(URL url, int modelID) { + return fillModel(modelID); + } + + private EncogModel fillModel(int modelID) { + EncogModel encogModel = new EncogModel("NN"); + BasicNetwork nn = models.get(modelID).getNetwork(); + + ArrayList<Double> weightsList = new ArrayList<>(); + String weights = nn.dumpWeights(); + String[] split = weights.split(","); + + for (int i = 0; i < (split.length); i++) { + weightsList.add(Double.valueOf(split[i])); + } + + encogModel.setWeights(weightsList); + + // do not use getLayers() because it is not restored immediately on load from file + FlatNetwork flat = nn.getFlat(); + List<Layer> layers = new ArrayList<>(flat.getLayerCounts().length); + logger.debug("layer counts: {}", Arrays.toString(flat.getLayerCounts())); + for (int j = 0; j < flat.getLayerCounts().length; j++) { +// boolean hasBias = j != 0 && j != flat.getLayerCounts().length - 1; + boolean hasBias = flat.getLayerCounts()[j] != flat.getLayerFeedCounts()[j]; + Layer l = new BasicLayer(flat.getActivationFunctions()[j], hasBias, flat.getLayerCounts()[j]); + l.setBiasActivation(flat.getBiasActivation()[j]); + layers.add(0, l); + } + + encogModel.setLayers(layers); + + return encogModel; + } + + + /** + * @param modelID + * @return + */ + public NormalizedField getNormalizerInput(int modelID, int columnNr) { + return models.get(modelID).getNormalizersIn().get(columnNr); + } + + /** + * @param modelID + * @return + */ + public NormalizedField getNormalizerTar(int modelID, int columnNr) { + return models.get(modelID).getNormalizersTar().get(columnNr); + } } diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Main.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Main.java index 24a5b62570a115fee266bc26cad2015656bee974..0f82201e8bcffb278656fb821d6efb3e55008791 100644 --- a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Main.java +++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Main.java @@ -1,8 +1,259 @@ package de.tudresden.inf.st.eraser.feedbackloop.learner; +import de.tudresden.inf.st.eraser.feedbackloop.api.Learner; +import de.tudresden.inf.st.eraser.feedbackloop.api.EncogModel; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import org.apache.commons.math3.stat.StatUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@SuppressWarnings("unused") public class Main { + private static final Logger logger = LogManager.getLogger(Main.class); + private static class InitialDataConfig { + static List<Integer> inputMins = Arrays.asList( + 7, // min month + 1, // min day + 10, // min hour + 1 // min minute + ); + static List<Integer> inputMaxes = Arrays.asList( + 9, // max month + 31, // max day + 21, // max hour + 60 // max minute + ); + static List<Integer> targetMins = Collections.singletonList(0); + static List<Integer> targetMaxes = Collections.singletonList(3); + static String csv_filename = "initial_data.csv"; + static String encog_filename = Paths.get("src", "main", "resources").toFile().getAbsolutePath() + "/"; + static int inputCount = 4; + static int outputCount = 1; + static int hiddenCount = 0; + static int hiddenNeuronCount = 7; + static List<Integer> targetColumns = Collections.singletonList(4); + } public static void main(String[] args) { - System.out.println("Hello World!"); +// loadFromCsv(); + loadFromEncog(); + } + + private static void loadFromCsv() { + Learner learner = new LearnerImpl(); + learner.loadDataSet(InitialDataConfig.csv_filename, InitialDataConfig.targetColumns,1); + learner.train( + InitialDataConfig.inputCount, InitialDataConfig.outputCount, InitialDataConfig.hiddenCount, + InitialDataConfig.hiddenNeuronCount, 1, InitialDataConfig.inputMaxes, InitialDataConfig.inputMins, + InitialDataConfig.targetMaxes, InitialDataConfig.targetMins); + + printModel(learner.getTrainedModel(1)); + } + + private static void loadFromEncog() { + Learner learner = new LearnerImpl(); + learner.loadModelFromFile( + new File(InitialDataConfig.encog_filename), 1, + InitialDataConfig.inputMaxes, InitialDataConfig.inputMins, + InitialDataConfig.targetMaxes, InitialDataConfig.targetMins); + printModel(learner.getTrainedModel(1)); + NeuralNetworkRoot eraserModel = LearnerHelper.transform(learner.getTrainedModel(1)); + } + + private static void printModel(EncogModel encogModel) { + logger.info("Model Type is: " + encogModel.getModelType()); + logger.info("Model Weights are: " + encogModel.getWeights()); + logger.info("Model layers are: " + encogModel.getLayers()); +// logger.info("Model input normal neutrons: " + model.getInputLayerNumber()); +// logger.info("Model input bias neutron: " + model.getInputBias()); +// logger.info("Model hidden normal neutrons: " + model.gethiddenLayerNumber()); +// logger.info("Model hidden bias neutron: " + model.getHiddenBias()); +// logger.info("Model output neutrons: " + model.getOutputLayerNumber()); +// logger.info("Model input activation function: " + model.getInputActivationFunction()); +// logger.info("Model hidden activation function: " + model.getHiddenActivationFunction()); +// logger.info("Model output activation function: " + model.getOutputActivationFunction()); + } + + //(ArrayList<Integer> list3 + private static Root createModel(ArrayList<Double> normalizedInputs, int inputBiasNumber) { + + //create KB Model + Root root = Root.createEmptyRoot(); + Group group = new Group(); + group.setID("Group1"); + root.getOpenHAB2Model().addGroup(group); + + NumberItem monthItem = new NumberItem(); + monthItem.setState(normalizedInputs.get(0)); + monthItem.setID("month"); + monthItem.setLabel("datetime-month"); + + NumberItem dayItem = new NumberItem(); + dayItem.setState(normalizedInputs.get(1)); + dayItem.setID("day"); + dayItem.setLabel("datetime-day"); + + NumberItem hourItem = new NumberItem(); + hourItem.setState(normalizedInputs.get(2)); + hourItem.setID("hour"); + hourItem.setLabel("datetime-hour"); + + NumberItem minuteItem = new NumberItem(); + minuteItem.setState(normalizedInputs.get(3)); + minuteItem.setID("minute"); + minuteItem.setLabel("datetime-minute"); + + if (inputBiasNumber == 1) { + NumberItem biasItem = new NumberItem(); + biasItem.setState(1); + biasItem.setID("bias"); + biasItem.setLabel("bias"); + group.addItem(biasItem); + } + group.addItem(monthItem); + group.addItem(dayItem); + group.addItem(hourItem); + group.addItem(minuteItem); + return root; + } + + /** + * Purpose: Create a neural network with 3 layers + */ + private static void createBrightnessNetwork(ArrayList<Double> all_weights, int inputBiasNumber, int hiddenlayernumber, + int hiddenlayerbias, ArrayList<Double> normalizedInputsandOutput) { + Root root = createModel(normalizedInputsandOutput, inputBiasNumber); + OpenHAB2Model model = root.getOpenHAB2Model(); + Item monthItem = model.resolveItem("month").orElseThrow( + () -> new RuntimeException("Month not found")); + Item dayItem = model.resolveItem("day").orElseThrow( + () -> new RuntimeException("Day not found")); + Item hourItem = model.resolveItem("hour").orElseThrow( + () -> new RuntimeException("Hour not found")); + Item minuteItem = model.resolveItem("minute").orElseThrow( + () -> new RuntimeException("Minute not found")); + Item biasItem = model.resolveItem("bias").orElseThrow( + () -> new RuntimeException("Bias not found")); + + NeuralNetworkRoot nn = new NeuralNetworkRoot(); + + // Activation Functions + DoubleArrayDoubleFunction sigmoid = inputs -> Math.signum(Arrays.stream(inputs).sum()); + DoubleArrayDoubleFunction tanh = inputs -> Math.tanh(Arrays.stream(inputs).sum()); + DoubleArrayDoubleFunction function_one = inputs -> 1.0; + + //Weights outputs from learner Module + ArrayList<Double> weights = all_weights; + // input layer + InputNeuron month = new InputNeuron(); + month.setItem(monthItem); + InputNeuron day = new InputNeuron(); + day.setItem(dayItem); + InputNeuron hour = new InputNeuron(); + hour.setItem(hourItem); + InputNeuron minute = new InputNeuron(); + minute.setItem(minuteItem); + InputNeuron bias = new InputNeuron(); + bias.setItem(biasItem); + + nn.addInputNeuron(month); + nn.addInputNeuron(day); + nn.addInputNeuron(hour); + nn.addInputNeuron(minute); + nn.addInputNeuron(bias); + + // output layer + OutputLayer outputLayer = new OutputLayer(); + OutputNeuron output0 = new OutputNeuron(); + output0.setActivationFormula(tanh); + OutputNeuron output1 = new OutputNeuron(); + output1.setActivationFormula(tanh); + OutputNeuron output2 = new OutputNeuron(); + output2.setActivationFormula(tanh); + OutputNeuron output3 = new OutputNeuron(); + output3.setActivationFormula(tanh); + + outputLayer.addOutputNeuron(output0); + outputLayer.addOutputNeuron(output1); + outputLayer.addOutputNeuron(output2); + outputLayer.addOutputNeuron(output3); + + outputLayer.setCombinator(inputs -> predictor(inputs, normalizedInputsandOutput)); + //outputLayer.setCombinator(inputs -> StatUtils.max(inputs) ); + nn.setOutputLayer(outputLayer); + + // hidden layer + int hiddenSum = hiddenlayernumber + hiddenlayerbias; + HiddenNeuron[] hiddenNeurons = new HiddenNeuron[hiddenSum]; + for (int i = 0; i < (hiddenNeurons.length); i++) { + if (i == hiddenlayernumber) { + HiddenNeuron hiddenNeuron = new HiddenNeuron(); + hiddenNeuron.setActivationFormula(function_one); + hiddenNeurons[i] = hiddenNeuron; + nn.addHiddenNeuron(hiddenNeuron); + bias.connectTo(hiddenNeuron, 1.0); + hiddenNeuron.connectTo(output0, weights.get(i)); + hiddenNeuron.connectTo(output1, weights.get(i + hiddenSum)); + hiddenNeuron.connectTo(output2, weights.get(i + hiddenSum * 2)); + hiddenNeuron.connectTo(output3, weights.get(i + hiddenSum * 3)); + } else { + HiddenNeuron hiddenNeuron = new HiddenNeuron(); + hiddenNeuron.setActivationFormula(tanh); + hiddenNeurons[i] = hiddenNeuron; + nn.addHiddenNeuron(hiddenNeuron); + + month.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length * 4) + i * 5)); + day.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length * 4 + 1) + i * 5)); + hour.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length * 4 + 2) + i * 5)); + minute.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length * 4 + 3) + i * 5)); + bias.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length * 4 + 4) + i * 5)); + hiddenNeuron.connectTo(output0, weights.get(i)); + hiddenNeuron.connectTo(output1, weights.get(i + hiddenSum)); + hiddenNeuron.connectTo(output2, weights.get(i + hiddenSum * 2)); + hiddenNeuron.connectTo(output3, weights.get(i + hiddenSum * 3)); + } + } + root.getMachineLearningRoot().setPreferenceLearning(nn); + System.out.println(root.prettyPrint()); + + List<String> output = new ArrayList<>(); + Function<DoubleNumber, String> leafToString = classification -> Double.toString(classification.number); + Function<NeuralNetworkRoot, DoubleNumber> classify = NeuralNetworkRoot::classify; + DoubleNumber classification = classify.apply(nn); + output.add(leafToString.apply(classification)); + System.out.println(output); + + + + + } + private static double predictor(double[] inputs, ArrayList<Double> normalizedInputsandOutputs){ + int index = 0; + double maxinput = StatUtils.max(inputs); + for (int i = 0; i < inputs.length; i++) { + if (inputs[i] == maxinput) { + index = i; + } + } + //outputs from learner + ArrayList<Double> outputs = new ArrayList<Double>(Arrays.asList(normalizedInputsandOutputs.get(4), + normalizedInputsandOutputs.get(5), normalizedInputsandOutputs.get(6), + normalizedInputsandOutputs.get(7))); + double output = outputs.get(index); + return output; + } + //[BasicMLData:0.0,0.0,0.0,0.0] + //inputs: + //[BasicMLData:-1.0,0.2666666666666666,-0.6363636363636364,-0.5593220338983051] + //outputs: + //[BasicMLData:0.36743405976714366,-0.6610085416169492,-0.9999999998849812,-0.9999999224507112] + } -} diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Network.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Network.java new file mode 100644 index 0000000000000000000000000000000000000000..e3467c7fda1544befee165d0316f11e9b35e8d27 --- /dev/null +++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/Network.java @@ -0,0 +1,202 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.encog.engine.network.activation.ActivationSigmoid; +import org.encog.ml.data.MLDataSet; +import org.encog.ml.data.basic.BasicMLDataSet; +import org.encog.ml.train.MLTrain; +import org.encog.neural.networks.BasicNetwork; +import org.encog.neural.networks.layers.BasicLayer; +import org.encog.neural.networks.training.propagation.back.Backpropagation; +import org.encog.persist.EncogDirectoryPersistence; +import org.encog.util.arrayutil.NormalizationAction; +import org.encog.util.arrayutil.NormalizedField; +import org.encog.util.simple.EncogUtility; + +/** + * Network class serves as interface to encog BasicNetwork and holdsfunctions for handling the BasicNetwork (training, input, output and inference) + * + * @author Bierzynski - initial contribution + */ +public class Network { + private static final Logger logger = LogManager.getLogger(Network.class); + private BasicNetwork network; + private int modelID; + private ArrayList<NormalizedField> normalizersIn; + private ArrayList<NormalizedField> normalizersTar; + + /** + * Constructor for when the neural network is created from data. + * + * @param inputCount number of neurons in the input layer + * @param outputCount number of neurons in the output layer + * @param hiddenCount number of hidden layers in the network + * @param hiddenNeuronCount number of neurons in the hidden layers for now + * @param modelID ID of the BasicNetwork. + * @param inputMaxes list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes list that contains max values of all output columns (results) e.g. brightness 100 for preference learning + * @param targetMins list that contains min values of all output columns (results) e.g. brightness 0 for preference learning + */ + public Network(int inputCount, int outputCount, int hiddenCount, int hiddenNeuronCount, int modelID, + List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + + normalizersIn = new ArrayList<>(); + normalizersTar = new ArrayList<>(); + this.modelID = modelID; + + network = new BasicNetwork(); + + network.addLayer(new BasicLayer(null, true, inputCount)); + + for (int i = 0; i < hiddenCount; i++) { + network.addLayer(new BasicLayer(new ActivationSigmoid(), true, hiddenNeuronCount)); + } + + network.addLayer(new BasicLayer(new ActivationSigmoid(), false, outputCount)); + network.getStructure().finalizeStructure(); + network.reset(); + + addNormalizer(inputMaxes, inputMins, normalizersIn); + addNormalizer(targetMaxes, targetMins, normalizersTar); + } + + private void addNormalizer(List<Integer> maxes, List<Integer> mins, ArrayList<NormalizedField> normalizers) { + for (int j = 0; j < maxes.size(); j++) { + NormalizedField normalizer = new NormalizedField("in_" + j, NormalizationAction.Normalize, + maxes.get(j), mins.get(j)); + normalizers.add(normalizer); + } + } + + /** + * Constructor for when the neural network is loaded from a file. + * Please note that the normalizer are note loaded file , because it is assumed that the mins and maxes are saved anyway in the meta data of the data sets or items. + * + * @param path path to the save folder of the model files e.g. C:\models\ + * @param modelID ID of the BasicNetwork. + * @param inputMaxes list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes list that contains max values of all output columns (results) e.g. brightness 100 for preference learning + * @param targetMins list that contains min values of all output columns (results) e.g. brightness 0 for preference learning + */ + public Network(String path, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + this(() -> (BasicNetwork) EncogDirectoryPersistence.loadObject(new File(path, "NN_" + modelID)), modelID, inputMaxes, inputMins, targetMaxes, targetMins); + } + + /** + * Constructor for when the neural network is loaded from an input stream. + * Please note that the normalizer are note loaded file , because it is assumed that the mins and maxes are saved anyway in the meta data of the data sets or items. + * + * @param input stream to load the model from + * @param modelID ID of the BasicNetwork. + * @param inputMaxes list that contains max values of all input columns (sensors) e.g. light intensity 100 + * @param inputMins list that contains min values of all input columns (sensors) e.g. light intensity 0 + * @param targetMaxes list that contains max values of all output columns (results) e.g. brightness 100 for preference learning + * @param targetMins list that contains min values of all output columns (results) e.g. brightness 0 for preference learning + */ + public Network(InputStream input, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + this(() -> (BasicNetwork) EncogDirectoryPersistence.loadObject(input), modelID, inputMaxes, inputMins, targetMaxes, targetMins); + } + + private Network(LoadEncogModel loader, int modelID, List<Integer> inputMaxes, List<Integer> inputMins, List<Integer> targetMaxes, + List<Integer> targetMins) { + this.modelID = modelID; + + normalizersIn = new ArrayList<>(); + normalizersTar = new ArrayList<>(); + + network = loader.load(); + + addNormalizer(inputMaxes, inputMins, normalizersIn); + addNormalizer(targetMaxes, targetMins, normalizersTar); + } + + @FunctionalInterface + interface LoadEncogModel { + BasicNetwork load(); + } + + /** + * Method to save the trained {@link BasicNetwork} to a file. + * File name is always NN_modelID + * + * @param savePath path to the folder in which the model file should be placed + */ + public void saveModel(String savePath) { + EncogDirectoryPersistence.saveObject(new File(savePath + "NN_" + modelID), network); + } + + /** + * Method for training the {@link BasicNetwork}. + * One row training is implemented here for now to optimize preference learning. + * + * @param input input part of the data row which should be used for training + * @param target target/output part of the data row which should be used for training + */ + public void train(List<Double> input, List<Double> target) { + double[][] INPUT = new double[1][input.size()]; + double[][] IDEAL = new double[1][target.size()]; + + for (int i = 0; i < input.size(); i++) { + INPUT[0][i] = normalizersIn.get(i).normalize(input.get(i)); + } + + for (int j = 0; j < target.size(); j++) { + IDEAL[0][j] = normalizersTar.get(j).normalize(target.get(j)); + } + + MLDataSet trainingSet = new BasicMLDataSet(INPUT, IDEAL); + MLTrain train = new Backpropagation(network, trainingSet, 3.5, 0.3); + + do { + train.iteration(); + } while (train.getError() > 0.005); + + train.finishTraining(); + } + + /** + * Method that uses the {@link BasicNetwork} to predict/classify/.. something based on an input. + * + * @param inputVector data that should be processed + */ + public double[] computeResult(List<Double> inputVector) { + double[] output = new double[normalizersTar.size()]; + double[] input = new double[inputVector.size()]; + + for (int i = 0; i < inputVector.size(); i++) { + input[i] = normalizersIn.get(i).normalize(inputVector.get(i)); + } + + network.compute(input, output); + + for (int j = 0; j < normalizersTar.size(); j++) { + output[j] = normalizersTar.get(j).deNormalize(output[j]); + } + + return output; + } + + public BasicNetwork getNetwork() { + return network; + } + + public ArrayList<NormalizedField> getNormalizersIn() { + return normalizersIn; + } + + public ArrayList<NormalizedField> getNormalizersTar() { + return normalizersTar; + } +} diff --git a/feedbackloop.learner/src/main/resources/1.eg b/feedbackloop.learner/src/main/resources/1.eg new file mode 100644 index 0000000000000000000000000000000000000000..03f16b277e8a85c539e7d18fa12bbf0a0b23bbcd --- /dev/null +++ b/feedbackloop.learner/src/main/resources/1.eg @@ -0,0 +1,24 @@ +encog,BasicNetwork,java,3.4.0,1,1549011876959 +[BASIC] +[BASIC:PARAMS] +[BASIC:NETWORK] +beginTraining=0 +connectionLimit=0 +contextTargetOffset=0,0,0 +contextTargetSize=0,0,0 +endTraining=2 +hasContext=f +inputCount=4 +layerCounts=4,8,5 +layerFeedCounts=4,7,4 +layerContextCount=0,0,0 +layerIndex=0,4,12 +output=0.7897697775,-0.4093295807,-0.9998836757,-1,0.8474456805,-0.2366377403,-0.9973602461,-0.9999999955,-0.4764290146,0.461275072,-0.0649442099,1,0,-0.6666666667,-0.4545454545,0.6949152542,1 +outputCount=4 +weightIndex=0,32,67 +weights=-1.1570349988,-0.8578879992,-1.0549159925,-3.7244521995,0.9574302229,-2.0931447828,0.896591315,-1.4483568363,1.4900254439,0.7366819855,0.6613521579,-0.609388338,-2.5552763401,1.2462544768,-0.6792334441,-3.3093626419,-2.155033701,-3.0549866186,-4.46456694,6.3689156226,-0.7553336123,-4.3167151702,-0.2075796591,-0.2387765659,-5.3558179654,0.3870219651,2.1254188856,-0.8070693427,16.3409581377,-0.0181269232,-3.5894371967,-0.6128690116,18.1886181385,-2.1919701152,0.7652143515,-0.5972333161,0.5485570992,2.8445160284,-3.1372144242,-6.0940916584,-12.1412546646,3.3344157392,-1.5344530226,4.1146550246,1.3873868104,1.3560253856,-0.8830370237,3.4646973293,0.45451005,19.2628819453,-1.4746031138,0.1239027404,0.5654616795,-0.3474956993,-0.7700441522,-0.2617909991,-0.9181158122,0.2756671819,0.0437957183,-7.3920564965,-0.4480327205,-2.520553979,-1.2363459836,-0.7189965057,1.2558119854,-0.5462053441,0.4060233037 +biasActivation=0,1,1 +[BASIC:ACTIVATION] +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationLinear" diff --git a/feedbackloop.learner/src/main/resources/NN_1 b/feedbackloop.learner/src/main/resources/NN_1 new file mode 120000 index 0000000000000000000000000000000000000000..c47a1c155ec39abc4357baa43dbafad60eb2988a --- /dev/null +++ b/feedbackloop.learner/src/main/resources/NN_1 @@ -0,0 +1 @@ +1.eg \ No newline at end of file diff --git a/feedbackloop.learner/src/main/resources/initial_data.csv b/feedbackloop.learner/src/main/resources/initial_data.csv new file mode 100644 index 0000000000000000000000000000000000000000..12c125ce2bbdc9ec6bb9762f05f6a83c3b863bb0 --- /dev/null +++ b/feedbackloop.learner/src/main/resources/initial_data.csv @@ -0,0 +1,418 @@ +7,20,12,13,2 +7,20,14,40,1 +7,20,14,40,2 +7,21,13,2,2 +7,21,13,2,2 +7,21,14,23,2 +7,21,14,23,2 +7,21,15,41,2 +7,21,16,54,2 +7,21,16,54,2 +7,21,17,45,3 +7,22,12,28,3 +7,22,15,35,2 +7,22,15,35,2 +7,22,18,59,3 +7,22,18,59,3 +7,23,12,32,2 +7,23,12,32,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,24,12,4,0 +7,24,12,4,0 +7,24,12,4,1 +7,24,14,38,2 +7,24,14,38,2 +7,24,18,54,3 +7,25,12,31,0 +7,25,12,32,1 +7,25,12,32,1 +7,25,15,6,3 +7,25,18,56,3 +7,26,13,41,2 +7,26,19,14,3 +7,27,11,39,2 +7,27,11,39,3 +7,27,11,46,3 +7,27,11,46,2 +7,27,13,8,2 +7,27,13,8,2 +7,27,13,9,2 +7,27,13,45,2 +7,27,13,45,2 +7,27,15,38,3 +7,28,12,12,2 +7,28,12,13,2 +7,28,12,41,2 +7,28,12,41,2 +7,28,12,41,2 +7,28,14,0,1 +7,28,14,0,2 +7,28,15,21,3 +7,28,18,56,3 +7,29,10,9,1 +7,29,10,9,1 +7,29,10,9,1 +7,29,11,54,0 +7,29,11,54,0 +7,29,11,54,0 +7,29,11,54,1 +7,29,14,10,2 +7,29,16,44,2 +7,29,16,44,2 +7,30,16,7,3 +7,30,18,45,3 +7,31,13,2,0 +7,31,13,2,1 +7,31,13,3,1 +7,31,13,3,1 +7,31,13,3,1 +7,31,18,39,3 +8,1,12,22,0 +8,1,12,22,1 +8,1,14,20,2 +8,1,14,20,2 +8,1,14,20,2 +8,1,15,55,3 +8,1,18,31,3 +8,1,18,37,3 +8,1,18,37,3 +8,1,19,2,3 +8,1,19,2,3 +8,1,20,5,3 +8,2,10,9,2 +8,2,10,9,1 +8,2,10,9,2 +8,2,10,9,2 +8,2,13,58,2 +8,2,13,58,2 +8,2,15,44,3 +8,2,15,44,3 +8,2,15,44,3 +8,2,17,21,3 +8,2,17,21,3 +8,2,17,21,3 +8,3,13,31,1 +8,3,13,31,2 +8,3,13,32,2 +8,3,16,43,3 +8,4,13,20,1 +8,4,13,20,2 +8,4,18,27,3 +8,5,13,37,2 +8,5,13,37,2 +8,5,18,33,3 +8,6,11,24,3 +8,6,11,24,3 +8,6,11,24,3 +8,6,13,50,3 +8,7,13,4,2 +8,7,13,4,2 +8,7,14,56,3 +8,8,12,13,2 +8,8,12,13,2 +8,8,15,51,2 +8,8,15,51,2 +8,8,15,51,3 +8,9,13,32,2 +8,9,13,32,2 +8,9,13,32,2 +8,9,15,8,2 +8,9,15,8,2 +8,9,15,8,2 +8,9,16,19,2 +8,10,11,32,0 +8,10,11,32,1 +8,10,11,32,1 +8,10,13,13,1 +8,10,13,13,1 +8,10,13,13,2 +8,10,16,42,3 +8,10,16,42,3 +8,11,14,6,2 +8,11,14,7,2 +8,11,18,54,3 +8,11,18,54,3 +8,11,18,54,3 +8,12,12,27,1 +8,12,12,27,1 +8,12,12,28,1 +8,12,13,53,2 +8,12,13,53,2 +8,12,13,53,2 +8,12,15,21,3 +8,13,13,16,1 +8,13,13,16,1 +8,13,13,16,1 +8,13,14,14,2 +8,13,14,14,2 +8,13,16,11,3 +8,13,17,18,3 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,2 +8,14,13,7,2 +8,14,15,6,3 +8,15,14,5,2 +8,15,14,5,2 +8,15,14,6,2 +8,15,14,6,2 +8,15,16,41,3 +8,15,16,41,3 +8,15,17,30,3 +8,16,13,40,2 +8,16,13,40,2 +8,16,17,52,3 +8,16,17,53,3 +8,17,13,34,1 +8,17,13,35,2 +8,17,14,7,2 +8,17,19,2,3 +8,18,10,21,3 +8,18,11,14,2 +8,18,11,14,2 +8,18,11,14,2 +8,18,11,14,2 +8,18,14,25,2 +8,18,14,25,3 +8,18,14,25,2 +8,18,18,18,3 +8,18,18,19,3 +8,19,18,33,3 +8,19,18,33,3 +8,19,18,33,3 +8,19,18,33,3 +8,20,14,28,2 +8,20,14,28,2 +8,20,14,28,2 +8,20,14,28,2 +8,20,17,8,3 +8,20,18,22,3 +8,21,11,24,1 +8,21,11,24,1 +8,21,11,24,1 +8,21,15,34,3 +8,21,18,55,3 +8,22,12,3,1 +8,22,12,4,2 +8,22,12,4,2 +8,22,13,51,2 +8,22,13,51,2 +8,22,13,51,2 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,40,3 +8,22,18,40,3 +8,23,13,42,1 +8,23,13,42,1 +8,23,17,32,3 +8,23,19,28,3 +8,23,20,27,3 +8,23,20,27,3 +8,23,21,49,3 +8,24,14,0,2 +8,24,14,0,2 +8,24,14,0,2 +8,24,14,0,2 +8,24,15,4,3 +8,24,15,4,3 +8,24,16,2,3 +8,24,16,3,3 +8,24,16,37,3 +8,24,17,9,3 +8,24,17,14,3 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,15,1,3 +8,25,17,58,3 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,16,42,3 +8,26,16,42,3 +8,26,18,41,3 +8,26,18,41,3 +8,27,13,41,2 +8,27,13,41,2 +8,27,13,41,2 +8,27,13,41,2 +8,27,17,42,3 +8,28,11,9,1 +8,28,11,9,1 +8,28,12,14,0 +8,28,12,14,1 +8,28,12,14,0 +8,28,15,3,2 +8,28,15,3,2 +8,28,16,31,3 +8,28,17,40,3 +8,29,14,44,3 +8,29,17,25,3 +8,30,12,5,0 +8,30,12,5,0 +8,30,12,5,0 +8,30,13,32,1 +8,30,13,32,1 +8,30,13,56,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,41,2 +8,30,14,41,2 +8,30,14,41,2 +8,30,15,50,3 +8,30,17,0,3 +8,30,18,59,3 +8,30,18,59,3 +8,31,14,31,2 +8,31,14,31,2 +8,31,14,31,2 +8,31,17,59,3 +8,31,18,0,3 +9,1,16,13,3 +9,1,16,13,3 +9,1,16,13,3 +9,1,17,41,3 +9,2,13,44,1 +9,2,13,44,1 +9,2,13,44,1 +9,2,14,49,2 +9,2,14,49,2 +9,2,14,49,2 +9,2,16,6,3 +9,2,16,6,3 +9,2,17,2,3 +9,3,16,9,3 +9,3,17,35,3 +9,3,17,36,3 +9,4,12,57,1 +9,4,12,57,1 +9,4,15,8,3 +9,4,15,34,3 +9,4,16,26,3 +9,4,16,26,3 +9,4,18,37,3 +9,4,18,37,3 +9,4,18,37,3 +9,6,11,18,0 +9,6,11,18,0 +9,6,12,54,1 +9,6,12,54,1 +9,6,14,21,2 +9,6,14,21,2 +9,6,19,20,3 +9,7,11,50,0 +9,7,14,17,2 +9,7,14,57,3 +9,7,14,57,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,18,38,3 +9,7,18,38,3 +9,8,11,4,2 +9,8,11,4,2 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,14,0 +9,8,11,14,1 +9,8,11,14,1 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,1 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,13,37,1 +9,8,13,37,1 +9,8,13,37,1 +9,8,14,20,2 +9,8,14,20,2 +9,8,18,20,3 +9,9,12,47,1 +9,9,12,47,2 +9,9,12,47,2 +9,9,19,5,3 +9,10,13,15,1 +9,10,13,15,1 +9,10,13,15,0 +9,10,16,49,3 +9,10,19,6,3 +9,10,21,5,3 +9,11,14,16,2 +9,11,14,16,2 +9,11,14,16,2 +9,11,18,41,3 +9,12,14,43,2 +9,12,14,43,2 +9,12,14,43,2 +9,12,16,14,3 +9,12,17,12,3 +9,12,17,12,2 +9,12,17,12,3 +9,12,17,12,2 +9,12,20,44,3 +9,13,19,52,3 +9,14,14,39,2 +9,14,14,39,2 +9,14,15,14,3 +9,14,17,29,3 +9,14,17,29,3 +9,14,17,29,3 +9,15,11,41,1 +9,15,11,41,1 +9,15,13,4,1 +9,15,14,3,1 +9,15,14,3,2 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,48,1 +9,16,12,48,1 +9,16,13,51,1 +9,16,13,51,2 +9,16,13,51,1 +9,16,15,13,3 +9,16,15,14,3 +9,16,15,14,3 +9,17,10,27,0 +9,17,10,27,0 +9,17,11,10,0 +9,17,11,10,0 +9,17,11,10,0 +9,17,12,43,1 +9,17,12,43,1 +9,17,12,43,1 +9,17,13,32,1 +9,17,13,32,1 +9,17,14,5,1 +9,17,14,5,2 +9,17,14,6,2 +9,17,15,7,3 +9,17,15,49,3 +9,17,15,49,3 +9,17,18,12,3 +9,17,18,13,3 diff --git a/feedbackloop.learner/src/main/resources/log4j2.xml b/feedbackloop.learner/src/main/resources/log4j2.xml index 481178a66aaaf356ef87241bf07871f4e808c3ee..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.learner/src/main/resources/log4j2.xml +++ b/feedbackloop.learner/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> @@ -19,4 +19,4 @@ <AppenderRef ref="RollingFile"/> </Root> </Loggers> -</Configuration> \ No newline at end of file +</Configuration> diff --git a/feedbackloop.learner_backup/.gitignore b/feedbackloop.learner_backup/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..54c5c0b024dab04da2a43d4135ed67ed2a670b46 --- /dev/null +++ b/feedbackloop.learner_backup/.gitignore @@ -0,0 +1,2 @@ +/build/ +logs/ diff --git a/feedbackloop.learner_backup/build.gradle b/feedbackloop.learner_backup/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..6199fa8da5b905d3d5d4cb02d6c6858218f3ffdd --- /dev/null +++ b/feedbackloop.learner_backup/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'application' + +dependencies { + compile project(':eraser-base') + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.2' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.2' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.encog', name: 'encog-core', version: '3.4' + implementation group: 'com.opencsv', name: 'opencsv', version: '4.1' + implementation group: 'commons-io', name: 'commons-io', version: '2.5' + implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.8' + implementation group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.8' + // https://mvnrepository.com/artifact/org.apache.spark/spark-mllib + //runtime group: 'org.apache.spark', name: 'spark-mllib_2.10', version: '1.3.0' + compile group: 'com.sparkjava', name: 'spark-core', version: '2.9.0' +} + +run { + mainClassName = 'de.tudresden.inf.st.eraser.feedbackloop.learner_backup.Main' + standardInput = System.in + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/CsvTransfer.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/CsvTransfer.java new file mode 100644 index 0000000000000000000000000000000000000000..1c08268d59dd4003149e551d2e3e60895d30e3f6 --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/CsvTransfer.java @@ -0,0 +1,60 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + +import java.io.*; +import java.util.*; + +import com.opencsv.CSVReader; +import com.opencsv.CSVWriter; + + +public class CsvTransfer { + public static void main(String[] args){ + Learner learner=new Learner(); + learner.train("datasets/backup/activity_data.csv","datasets/backup/preference_data.csv"); + } + + //learner.train("datasets/backup/activity_data.csv","datasets/backup/preference_data.csv"); + /**private static final String CSV_FILE_PATH + = "datasets/backup/activity_data_example.csv"; + private static final String OUTPUT_FILE_PATH + = "datasets/backup/activity_data.csv"; + public static void main(String[] args) + { + addDataToCSV(CSV_FILE_PATH, OUTPUT_FILE_PATH); + } + public static void addDataToCSV(String input, String output) + { + File input_file = new File(input); + File output_file = new File(output); + try { + // create FileWriter object with file as parameter + FileReader reader = new FileReader(input_file); + CSVReader csv_reader = new CSVReader(reader); + String[] nextRecord; + FileWriter writer = new FileWriter(output_file); + CSVWriter csv_writer = new CSVWriter(writer, ',', + CSVWriter.NO_QUOTE_CHARACTER, + CSVWriter.DEFAULT_ESCAPE_CHARACTER, + CSVWriter.DEFAULT_LINE_END); + List<String[]> data = new ArrayList<String[]>(); + + while ((nextRecord = csv_reader.readNext()) != null) { + data.add(nextRecord); + + for (String cell : nextRecord) { + System.out.print(cell + "\t"); + } + System.out.println(); + } + csv_writer.writeAll(data); + writer.close(); + csv_reader.close(); + } + catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }*/ +} + + diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/DummyPreference.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/DummyPreference.java new file mode 100644 index 0000000000000000000000000000000000000000..59f3ad9f43fd11eab315e145b1780d4152b82181 --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/DummyPreference.java @@ -0,0 +1,75 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; +import com.opencsv.CSVWriter; + +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class DummyPreference { + private static String activity; //Activity: walking, reading, working, dancing, lying, getting up + private static String watch_brightness; //dark: <45; dimmer 45-70; bright >70; + private static String light_color_openhab_H; //red 7; green 120; blue 240; yellow 60; sky blue 180; purple 300; + private static String brightness_output; //1-100**/ + private static Random random = new Random(); + + public static void main(String[] args) { + creator(); + } + static void creator(){ + + + try{ + FileWriter writer = new FileWriter("datasets/backup/preference_data.csv",true); + CSVWriter csv_writer = new CSVWriter(writer, ',', + CSVWriter.NO_QUOTE_CHARACTER, + CSVWriter.DEFAULT_ESCAPE_CHARACTER, + CSVWriter.DEFAULT_LINE_END); + + + //activity="walking" green + activity ="walking"; + + // activity ="reading"; + csv_writer.writeAll(generator("walking","green")); + csv_writer.writeAll(generator("reading","sky blue")); + csv_writer.writeAll(generator("working","blue")); + csv_writer.writeAll(generator("dancing","purple")); + csv_writer.writeAll(generator("lying","red")); + csv_writer.writeAll(generator("getting up","yellow")); + csv_writer.close(); + writer.close(); + }catch (IOException e){e.printStackTrace();} + } + static List<String[]> generator(String activity_input, String color){ + List<String[]> data = new ArrayList<String[]>(); + activity = activity_input; + light_color_openhab_H =color; + //100 walking with different lighting intensity + for (int i=0; i<100; i++){ + String[] add_data = new String[4]; + int brightness = random.nextInt(3000); + System.out.println(brightness); + if (brightness<45){ + watch_brightness = "dark"; + brightness_output ="100"; + }else if(45<=brightness && brightness<200){ + watch_brightness = "dimmer"; + brightness_output ="40"; + }else if( 200<=brightness && brightness<1000){ + watch_brightness = "medium"; + brightness_output ="70"; + }else{ + watch_brightness = "bright"; + brightness_output ="0"; + } + add_data[0] = activity; + add_data[1] = watch_brightness; + add_data[2] = light_color_openhab_H; + add_data[3] = brightness_output; + data.add(add_data); + } + return data; + } +} diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java new file mode 100644 index 0000000000000000000000000000000000000000..5d8021d8faf7519d9ec15305f0752cfbcd742a0c --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java @@ -0,0 +1,191 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + +import java.io.File; +import java.io.IOException; +//import java.util.ArrayList; +//import com.sun.javafx.tools.packager.Log; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +//import org.encog.ConsoleStatusReportable; +import org.encog.Encog; +import org.encog.ml.MLClassification; +//import org.encog.ml.MLRegression; +import org.encog.ml.data.MLData; +import org.encog.ml.data.versatile.NormalizationHelper; +import org.encog.ml.data.versatile.VersatileMLDataSet; +import org.encog.ml.data.versatile.columns.ColumnDefinition; +import org.encog.ml.data.versatile.columns.ColumnType; +import org.encog.ml.data.versatile.sources.CSVDataSource; +import org.encog.ml.data.versatile.sources.VersatileDataSource; +import org.encog.ml.factory.MLMethodFactory; +import org.encog.ml.model.EncogModel; +import org.encog.neural.networks.BasicNetwork; +import org.encog.util.csv.CSVFormat; +import static org.encog.persist.EncogDirectoryPersistence.*; + +public class Learner { + + /** + * intial train + * */ + private File save_activity_model_file; + private File save_preference_model_file; + private File csv_file; + private VersatileMLDataSet a_data; + private VersatileMLDataSet p_data; + private EncogModel a_model; + private EncogModel p_model; + private NormalizationHelper activity_helper; + private NormalizationHelper preference_helper; + private MLClassification a_best_method; + private MLClassification p_best_method; + private final Logger logger = LogManager.getLogger(Learner.class); + + public Learner() { + try { + save_activity_model_file = File.createTempFile("activity_model", "eg"); + } catch (IOException e) { + // use local alternative + save_activity_model_file = new File("activity_model.eq"); + } + try { + save_preference_model_file = File.createTempFile("preference_model", "eg"); + } catch (IOException e) { + // use local alternative + save_preference_model_file = new File("preference_model.eg"); + } + save_activity_model_file.deleteOnExit(); + save_preference_model_file.deleteOnExit(); + } + + private void activityDataAnalyser(String activity_csv_url){ + VersatileDataSource a_souce; + String csv_url_activity; + csv_url_activity = activity_csv_url; + this.csv_file = new File(csv_url_activity); + a_souce = new CSVDataSource(csv_file,false,CSVFormat.DECIMAL_POINT); + a_data = new VersatileMLDataSet(a_souce); + String[] activity_inputs={"m_accel_x","m_accel_y", "m_accel_z", + "m_rotation_x","m_rotation_y", "m_rotation_z", + "w_accel_x","w_accel_y", "w_accel_z", + "w_rotation_x","w_rotation_y","w_rotation_z" + }; + for(int i=0; i < activity_inputs.length; i++){ + a_data.defineSourceColumn(activity_inputs[i], i, ColumnType.continuous); + } + ColumnDefinition outputColumn = a_data.defineSourceColumn("labels", 12, ColumnType.nominal); + a_data.defineSingleOutputOthersInput(outputColumn); + a_data.analyze(); + a_model = new EncogModel(a_data); + a_model.selectMethod(a_data, MLMethodFactory.TYPE_FEEDFORWARD); + a_data.normalize(); + activity_helper = a_data.getNormHelper(); + } + + private void preferenceDataAnalyser(String preference_csv_url){ + VersatileDataSource p_source; + String csv_url_preference; + csv_url_preference = preference_csv_url; + this.csv_file = new File(csv_url_preference); + p_source = new CSVDataSource(csv_file,false,CSVFormat.DECIMAL_POINT); + p_data = new VersatileMLDataSet(p_source); + p_data.defineSourceColumn("activity", 0, ColumnType.nominal); + p_data.defineSourceColumn("w_brightness", 1, ColumnType.nominal); + ColumnDefinition outputColumn1 = p_data.defineSourceColumn("label1", 2, ColumnType.continuous); + ColumnDefinition outputColumn2 = p_data.defineSourceColumn("label2", 3, ColumnType.continuous); + ColumnDefinition[] outputs = new ColumnDefinition[2]; + outputs[0] = outputColumn1; + outputs[1] = outputColumn2; + p_data.defineMultipleOutputsOthersInput(outputs); + p_data.analyze(); + p_model = new EncogModel(p_data); + p_model.selectMethod(p_data, MLMethodFactory.TYPE_FEEDFORWARD); + p_data.normalize(); + preference_helper = p_data.getNormHelper(); + } + + void train(String activity_url,String preference_url){ + activity_train(activity_url); + preference_train(preference_url); + Encog.getInstance().shutdown(); + + } + + void activity_train(String activity_csv_url){ + logger.info("Activity training is beginning ... ..."); + activityDataAnalyser(activity_csv_url); + a_model.holdBackValidation(0.3, true, 1001); + a_model.selectTrainingType(a_data); + a_best_method = (MLClassification)a_model.crossvalidate(5, true); + saveEncogModel(save_activity_model_file); + logger.info("Activity training is finished ... ..."); + Encog.getInstance().shutdown(); + } + void preference_train(String prefence_csv_url){ + logger.info("Preference training is beginning ... ..."); + preferenceDataAnalyser(prefence_csv_url); + p_model.holdBackValidation(0.3, true, 1001); + p_model.selectTrainingType(p_data); + p_best_method = (MLClassification)p_model.crossvalidate(5, true); + saveEncogModel(save_preference_model_file); + logger.info("Preference training is finished ... ..."); + Encog.getInstance().shutdown(); + } + + String[] predictor(String[] new_data){ + String[] preference_data = new String[2]; + String[] result = new String[3]; + String[] activity_data= new String[12]; + for(int i=0; i<new_data.length;i++){ + activity_data[i]=new_data[i]; + } + result[0] = activity_predictor(activity_data); + preference_data[0]=result[0]; + preference_data[1]=new_data[12]; + result[1] = preference_predictor(preference_data)[0]; + result[2] = preference_predictor(preference_data)[1]; + Encog.getInstance().shutdown(); + return result; + } + + String activity_predictor(String[] new_data){ + logger.info("Activity predicting ... ..."); + String activity_result; + activityDataAnalyser("../datasets/backup/activity_data.csv"); + BasicNetwork activity_method = (BasicNetwork) loadObject(save_activity_model_file); + MLData input = activity_helper.allocateInputVector(); + activity_helper.normalizeInputVector(new_data,input.getData(),false); + MLData output = activity_method.compute(input); + activity_result = activity_helper.denormalizeOutputVectorToString(output)[0]; + Encog.getInstance().shutdown(); + logger.debug("Activity Predictor result is: {}",activity_result); + return activity_result; + } + + String[] preference_predictor(String[] new_data){ + logger.info("Activity predicting ... ..."); + String[] preference_result; + preference_result = new String[2]; + preferenceDataAnalyser("../datasets/backup/preference_data.csv"); + BasicNetwork preference_method = (BasicNetwork)loadObject(save_preference_model_file); + MLData input = preference_helper.allocateInputVector(); + preference_helper.normalizeInputVector(new_data, input.getData(),false); + MLData output = preference_method.compute(input); + preference_result[0] = preference_helper.denormalizeOutputVectorToString(output)[0]; + preference_result[1] = preference_helper.denormalizeOutputVectorToString(output)[1]; + Encog.getInstance().shutdown(); + logger.debug("Preference Predictor result is, Color: {}",Math.round(Float.valueOf(preference_result[0]))); + logger.debug("Preference Predictor result is, Brightness: {} ",Math.round(Float.valueOf(preference_result[1]))); + return preference_result; + } + private void saveEncogModel(File modelFile){ + if (modelFile.equals(save_activity_model_file)) { + saveObject(modelFile, this.a_best_method); + } else { + saveObject(modelFile, this.p_best_method); + } + + } +} + + diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..dc16aa2353ea216a49d2d1e75d8cfcff4d36abbc --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java @@ -0,0 +1,211 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + + + +//import com.sun.javafx.tools.packager.Log; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + + +public class MachineLearningImpl implements MachineLearningDecoder, MachineLearningEncoder { + + private List<Item> target_item_list; + private List<Item> relevant_item_list; + private String[] preference_result; + + private Root root; + private final Logger logger = LogManager.getLogger(MachineLearningImpl.class); + private final Learner learner; + private final int goal; + + public static final int GOAL_ACTIVITY_PHONE_AND_WATCH = 1; + public static final int GOAL_PREFERENCE_BRIGHTNESS_IRIS = 2; + public String activity_result; + private String[] activites= new String[]{"working", "walking", "dancing", "lying", "getting up", "reading"}; + public MachineLearningImpl(Learner learner, int goal) { + this.learner = learner; + this.goal = goal; + } + + @Override + public void setKnowledgeBaseRoot(Root root) { + this.root = root; + updateItems(); + } + + private void updateItems() { + OpenHAB2Model model = root.getOpenHAB2Model(); + List<String> targetItemNames, relevantItemNames; + switch (this.goal) { + case GOAL_ACTIVITY_PHONE_AND_WATCH: + targetItemNames = Collections.singletonList("activity"); + relevantItemNames = Arrays.asList( + "m_accel_x", + "m_accel_y", + "m_accel_z", + "m_rotation_x", + "m_rotation_y", + "m_rotation_z", + "w_accel_x", + "w_accel_y", + "w_accel_z", + "w_rotation_x", + "w_rotation_y", + "w_rotation_z" + ); + break; + case GOAL_PREFERENCE_BRIGHTNESS_IRIS: + targetItemNames = Collections.singletonList("iris1_item"); + relevantItemNames = Arrays.asList( + "activity", + "w_brightness" + ); + break; + default: + logger.error("Unknown goal value ({}) set", this.goal); + targetItemNames = Collections.emptyList(); + relevantItemNames = Collections.emptyList(); + } + target_item_list = targetItemNames.stream() + .map(name -> resolve(model, name)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + relevant_item_list = relevantItemNames.stream() + .map(name -> resolve(model, name)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private Item resolve(OpenHAB2Model model, String id) { + Optional<Item> maybeItem = model.resolveItem(id); + if (maybeItem.isPresent()) { + return maybeItem.get(); + } else { + logger.warn("Could not find item with id {}", id); + return null; + } + } + + @Override + public void newData(List<Item> changedItems) { + /* String topic = changedItems.get(0).getTopic().toString(); + if(topic.equals("oh2/samsung/items1")){ + new_data[0]=changedItems.get(0).influxMeasurementName(); + } + model.getOpenHAB2Model().items(); + Item iris_item=model.getOpenHAB2Model().resolveItem("iris_item").get(); + iris_item.getStateAsString();*/ + /* FIXME either save state of unchanged items here (if only changed items are reported) <- pull model + or let knowledge base pass all relevant items <- push model + */ + if(this.goal==GOAL_ACTIVITY_PHONE_AND_WATCH){ + String[] new_data = new String[12]; + for (int i =0; i< new_data.length; i++){ + new_data[i] ="0"; + } + for(Item item: changedItems){ + int i = 0; + for(Item item1: relevant_item_list){ + if(item.getTopic().toString().equals(item1.getTopic().toString())){ + new_data[i]=item.getStateAsString(); + } + i++; + } + } + this.activity_result = learner.activity_predictor(new_data); + + }else if(this.goal==GOAL_PREFERENCE_BRIGHTNESS_IRIS){ + String[] new_data = new String[2]; + for(Item item: changedItems){ + if(root.getOpenHAB2Model().getActivityItem().equals(item)) + { + String test=item.getStateAsString(); + int index = Math.round(Float.valueOf(test)); + new_data[0]=activites[index]; + } + if(item.getID().equals("w_brightness")){ + new_data[1]=item.getStateAsString(); + } + } + this.preference_result=learner.preference_predictor(new_data); + } + } + + @Override + public List<Item> getTargets() { + return target_item_list; + } + + @Override + public List<Item> getRelevantItems() { + return relevant_item_list; + } + + @Override + public void triggerTraining() { + logger.debug("Ignore trigger training call"); + } + + @Override + public Instant lastModelUpdate() { + return null; + } + + @Override + public MachineLearningResult classify() { + switch (this.goal) { + case GOAL_ACTIVITY_PHONE_AND_WATCH: + String activityStringValue = activity_result; + Item activityItem = resolve(this.root.getOpenHAB2Model(), "activity"); + //activityItem.setStateFromString(activityStringValue); + // FIXME how to translate activityStringValue to a number? or should activity item state better be a String? + for (int i=0; i< activites.length;i++){ + if(activites[i].equals(activityStringValue)){ + activityItem.setStateFromString(String.valueOf(i)); + } + } + logger.debug("Classify would return activity: {}", activityStringValue); + ItemPreference classifiedActivity = new ItemPreferenceDouble(activityItem, 0); + return new MachineLearningResultImpl(classifiedActivity); + case GOAL_PREFERENCE_BRIGHTNESS_IRIS: +// String[] preference = {result[1], result[2]}; + // FIXME what is the meaning of result[1] and result[2] + Item iris1 = resolve(this.root.getOpenHAB2Model(), "iris1_item"); + int color = 0; + int brightness = 0; + if (preference_result != null){ + color = Math.round(Float.valueOf(preference_result[0])); + brightness = Math.round(Float.valueOf(preference_result[1])); + } + + ItemPreference classifiedPreference = new ItemPreferenceColor(iris1, TupleHSB.of(color, 100, brightness)); + return new MachineLearningResultImpl(classifiedPreference); + default: + logger.error("Unknown goal value ({}) set in classify", this.goal); + return new EmptyMachineLearningResult(); + } + } + + public void initActivities(String filenameOfCsv) { + logger.debug(filenameOfCsv); + learner.activity_train(filenameOfCsv); + } + + public void initPreferences(String filenameOfCsv) { + learner.preference_train(filenameOfCsv); + } + + class EmptyMachineLearningResult implements MachineLearningResult { + + @Override + public List<ItemPreference> getPreferences() { + return Collections.emptyList(); + } + } + +} diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningResultImpl.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningResultImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..61d483a566255d51ce424d23adb014df1eec8109 --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningResultImpl.java @@ -0,0 +1,26 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + +import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference; +import de.tudresden.inf.st.eraser.jastadd.model.MachineLearningResult; + +import java.util.Collections; +import java.util.List; + +/** + * TODO: Add description. + * + * @author rschoene - Initial contribution + */ +public class MachineLearningResultImpl implements MachineLearningResult { + + private final ItemPreference preference; + + MachineLearningResultImpl(ItemPreference preference) { + this.preference = preference; + } + + @Override + public List<ItemPreference> getPreferences() { + return Collections.singletonList(preference); + } +} diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..2580847d5ace8e832bde76159dad2e85974115a2 --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java @@ -0,0 +1,164 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + +import org.encog.util.csv.CSVFormat; +import org.encog.util.csv.ReadCSV; + +import java.util.Arrays; + +public class Main { + + public static void main(String[] args) { + /** + * new data from KB + * */ + //ReaderCSV reader = new ReaderCSV("datasets/backup/activity_data.csv","preference"); + //reader.updater(); + Learner learner=new Learner(); + //learner.preference_train("../datasets/backup/preference_data.csv"); + //learner.train("datasets/backup/activity_data.csv","datasets/backup/preference_data.csv"); + //0.5793968,1.2126632,-4.6244006,-0.030779,0.801127,0.590978,-3.1411927,-0.93373865,-0.31124622,-0.35992432,0.33746338,-0.79608154,dancing + /**String[] new_data = new String[12]; + new_data[0]="0.5793968"; + new_data[1]="1.2126632"; + new_data[2]="-4.6244006"; + new_data[3]="-0.030779"; + new_data[4]="0.801127"; + new_data[5]="0.590978"; + new_data[6]="-3.1411927"; + new_data[7]="-0.93373865"; + new_data[8]="-0.31124622"; + new_data[9]="-0.35992432"; + new_data[10]="0.33746338"; + new_data[11]="-0.79608154"; + + String result =learner.activity_predictor(new_data); + System.out.println(result); + + List k=new ArrayList(); + + k.add(new_data);*/ + + //learner.preference_train("datasets/backup/preference_data.csv"); + //learner.train("datasets/backup/activity_data.csv","datasets/backup/preference_data.csv"); + //walking,medium,120,70 + //reading,bright,180,0 + + //activity_validation_learner(); + //0.10654198,8.6574335,4.414908,0.040269,0.516884,0.853285,1.2066777,-1.1444284,9.648633,1.2207031E-4,-0.055358887,0.5834961 working + String[] new_data = new String[12]; + new_data[0]="0.010773907"; + new_data[1]="8.610746"; + new_data[2]="4.4963107"; + new_data[3]="0.047136"; + new_data[4]="0.515427"; + new_data[5]="0.852877"; + new_data[6]="0.9720459"; + new_data[7]="-1.3694834"; + new_data[8]="9.696517"; + new_data[9]="-0.0056152344"; + new_data[10]="-0.049438477"; + new_data[11]="0.5576782"; + //0.010773907,8.610746,4.4963107,0.047136,0.515427,0.852877,0.9720459,-1.3694834,9.696517,-0.0056152344,-0.049438477,0.5576782 working + //0.9999999988939648,-0.9995820798966354,-0.9999999999999997,-0.9999988062118802,-0.9974031940544938,-1.0 + + //String result =learner.activity_predictor(new_data); + //System.out.println(result); + + /**String[] new_data_1 =new String[12]; + //-2.6252422,8.619126,-2.7030537,0.552147,0.5078,0.450302,-8.1881695,-1.2641385,0.038307227,-0.34222412,0.49102783,-0.016540527,walking + new_data_1[0]="-2.6252422"; + new_data_1[1]="8.619126"; + new_data_1[2]="-2.7030537"; + new_data_1[3]="0.552147"; + new_data_1[4]="0.5078"; + new_data_1[5]="0.450302"; + new_data_1[6]="-8.1881695"; + new_data_1[7]="-1.2641385"; + new_data_1[8]="0.038307227"; + new_data_1[9]="-0.34222412"; + new_data_1[10]="0.49102783"; + new_data_1[11]="-0.016540527"; + String result1 =learner.activity_predictor(new_data_1); + System.out.println(result1); + /** + * learner.train(activity_csv_url, preference_data_url) + * learner.predictor get the result from predictor for new data + * */ + /**String[] new_data_2 = new String[12]; + //-6.5565214,5.717354,5.6658783,0.185591,0.464146,0.413321,-20.580557,3.8498764,-0.4261679,0.7647095,-0.4713745,0.23999023,dancing + new_data_2[0]="-6.5565214"; + new_data_2[1]="5.717354"; + new_data_2[2]="5.6658783"; + new_data_2[3]="0.185591"; + new_data_2[4]="0.464146"; + new_data_2[5]="0.413321"; + new_data_2[6]="-20.580557"; + new_data_2[7]="3.8498764"; + new_data_2[8]="-0.4261679"; + new_data_2[9]="0.7647095"; + new_data_2[10]="-0.4713745"; + new_data_2[11]="0.23999023"; + + String[] new_data_3 = new String[12]; + new_data_3[0]="-5.3881507"; + new_data_3[1]="0.25378537"; + new_data_3[2]="7.69257"; + new_data_3[3]="-0.122974"; + new_data_3[4]="0.247411"; + new_data_3[5]="0.439031"; + new_data_3[6]="4.9224787"; + new_data_3[7]="-10.601525"; + new_data_3[8]="-4.927267"; + new_data_3[9]="0.7946167"; + new_data_3[10]="0.35272217"; + new_data_3[11]="0.16192627"; + //"-5.3881507","0.25378537","7.69257","-0.122974","0.247411","0.439031","4.9224787","-10.601525","-4.927267","0.7946167","0.35272217","0.16192627","lying" + Learner learner=new Learner(); + //learner.train("datasets/backup/activity_data.csv", "datasets/preference_data.csv"); + String[] result = learner.predictor(new_data_3); + System.out.println("activity is:" + result[0]); + //System.out.println("perference is: "+ result[1]);**/ + } + public static void activity_validation_learner(){ + ReadCSV csv = new ReadCSV("datasets/backup/activity_data.csv", false, CSVFormat.DECIMAL_POINT); + String[] line = new String[12]; + Learner learner=new Learner(); + int wrong=0; + int right=0; + while(csv.next()) { + StringBuilder result = new StringBuilder(); + line[0] = csv.get(0); + line[1] = csv.get(1); + line[2] = csv.get(2); + line[3] = csv.get(3); + line[4] = csv.get(4); + line[5] = csv.get(5); + line[6] = csv.get(6); + line[7] = csv.get(7); + line[8] = csv.get(8); + line[9] = csv.get(9); + line[10] = csv.get(10); + line[11] = csv.get(11); + String correct = csv.get(12); + String irisChosen = learner.predictor(line)[0]; + result.append(Arrays.toString(line)); + result.append(" -> predicted: "); + result.append(irisChosen); + result.append("(correct: "); + result.append(correct); + result.append(")"); + if (irisChosen.equals(correct)!=true){ + System.out.println(correct); + System.out.println(irisChosen); + ++wrong; + }else{ + ++right; + } + System.out.println(result.toString()); + } + System.out.println("wrong number"+wrong); + System.out.println("right number"+right); + //double validation = (double(right))/(double(wrong+right)); + //System.out.println("%.2f"+validation); + } +} diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/ReaderCSV.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/ReaderCSV.java new file mode 100644 index 0000000000000000000000000000000000000000..76c1ce791b00843befc9ed8b9d0f1941532ac386 --- /dev/null +++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/ReaderCSV.java @@ -0,0 +1,116 @@ +package de.tudresden.inf.st.eraser.feedbackloop.learner_backup; + +import java.awt.*; +import java.io.FileReader; +import java.io.*; +import java.util.Arrays; + +import com.opencsv.CSVReader; +import org.apache.http.client.fluent.Request; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class ReaderCSV{ + + //read every 5 s from csv + //activity CSV + /** + * Col 1: smartphone acceleration x + * Col 2: smartphone acceleration y + * Col 3: smartphone acceleration z + * Col 4: smartphone rotation x + * Col 5: smartphone rotation y + * Col 6: smartphone rotation z + * Col 7: watch acceleration x + * Col 8: watch acceleration y + * Col 9: watch acceleration z + * Col 10: watch rotation x + * Col 11: watch rotation y + * Col 12: watch rotation z/*/ + //preference CSV + /** + * Col 1: Activity + * Col 2: watch brightness range "bright, medium, dimmer, dark"*/ + + private static int TIME_PERIOD = 5000; //5 second update new value + private String csv_file_path; + private String csv_typ; + private File file; + private FileReader file_reader; + private CSVReader csv_reader; + private String[] next_record; + private static final Logger logger = LogManager.getLogger(ReaderCSV.class); + private static final String ERASER_ITEM_URI = "http://localhost:4567/model/items/"; + //TODO ITEM_NAME HAS TO BE DISCUSSED + private static final String[] ACTIVITY_ITEM_NAME = { + "m_accel_x", "m_accel_y", "m_accel_z", "m_rotation_x", + "m_rotation_y", "m_rotation_z", "w_accel_x", "w_accel_y", + "w_accel_z", "w_rotation_x", "w_rotation_y", "w_rotation_z"}; + private static final String[] PREFERENCE_ITEM_NAME = { + "w_brightness" + }; + + //csv_type is activity or preference + public ReaderCSV(String csv_file_path, String csv_type) { + this.csv_file_path = csv_file_path; + this.csv_typ = csv_type; + } + + public void updater(){ + file=new File(csv_file_path); + try { + file_reader =new FileReader(file); + csv_reader = new CSVReader(file_reader); + while ((next_record = csv_reader.readNext()) != null) { + Thread.sleep(TIME_PERIOD); + if (csv_typ =="activity"){ + String[] values = Arrays.copyOf(next_record,12); + setNewValue(values, ACTIVITY_ITEM_NAME); + + } else { + String[] values = Arrays.copyOf(next_record,2); + setNewValue(values, PREFERENCE_ITEM_NAME); + } + } + } + catch (Exception e){ + e.printStackTrace(); + } + } + private void setNewValue(String[] values, String[] name){ + if(this.csv_typ.equals("activity")) + { + int i = 0; + for(String value : values){ + String uri = ERASER_ITEM_URI + name[i] + "/state"; + logger.info("reader:",value); + try { + HttpResponse httpResponse = Request.Put(uri) + .bodyString(value, ContentType.TEXT_PLAIN) + .execute().returnResponse(); + }catch (Exception e){ + e.printStackTrace(); + } + i+=1; + } + + }else{ + String uri= ERASER_ITEM_URI + "w_brightness" +"/state"; + logger.info("reader:",values[1]); + try { + HttpResponse httpResponse = Request.Put(uri) + .bodyString(values[1], ContentType.TEXT_PLAIN) + .execute().returnResponse(); + }catch (Exception e){ + e.printStackTrace(); + } + + } + + } +} \ No newline at end of file diff --git a/feedbackloop.learner_backup/src/main/resources/log4j2.xml b/feedbackloop.learner_backup/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c --- /dev/null +++ b/feedbackloop.learner_backup/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Console"> + <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> + </Console> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> + <Policies> + <OnStartupTriggeringPolicy/> + </Policies> + <DefaultRolloverStrategy max="20"/> + </RollingFile> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Root> + </Loggers> +</Configuration> diff --git a/feedbackloop.main/.gitignore b/feedbackloop.main/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.main/.gitignore +++ b/feedbackloop.main/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.main/build.gradle b/feedbackloop.main/build.gradle index 2287b7b94c161ad8a3268ca9d751e1c1e2137e88..7e66daa3efddb2ef62735abc2fe6e57bab91d053 100644 --- a/feedbackloop.main/build.gradle +++ b/feedbackloop.main/build.gradle @@ -1,10 +1,3 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { @@ -14,11 +7,6 @@ dependencies { compile project(':feedbackloop.analyze') compile project(':feedbackloop.plan') compile project(':feedbackloop.execute') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/feedbackloop.main/src/main/resources/log4j2.xml b/feedbackloop.main/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.main/src/main/resources/log4j2.xml +++ b/feedbackloop.main/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/feedbackloop.monitor/.gitignore b/feedbackloop.monitor/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.monitor/.gitignore +++ b/feedbackloop.monitor/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.monitor/build.gradle b/feedbackloop.monitor/build.gradle index 77b9513097ddb84f3d1e003489bade769e38c4a7..e3f4c6e283e32e73a0a54ffe1f03d019c81c6d8a 100644 --- a/feedbackloop.monitor/build.gradle +++ b/feedbackloop.monitor/build.gradle @@ -1,20 +1,8 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') compile project(':feedbackloop.api') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/feedbackloop.monitor/src/main/resources/log4j2.xml b/feedbackloop.monitor/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.monitor/src/main/resources/log4j2.xml +++ b/feedbackloop.monitor/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/feedbackloop.plan/.gitignore b/feedbackloop.plan/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/feedbackloop.plan/.gitignore +++ b/feedbackloop.plan/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/feedbackloop.plan/build.gradle b/feedbackloop.plan/build.gradle index 2a9ac05f8f3beb83fab66354c598d27ca84bb4b7..13b20cf6cff29796338f0ac62dd71723fd5ff467 100644 --- a/feedbackloop.plan/build.gradle +++ b/feedbackloop.plan/build.gradle @@ -1,20 +1,8 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') compile project(':feedbackloop.api') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/feedbackloop.plan/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/plan/PlanImpl.java b/feedbackloop.plan/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/plan/PlanImpl.java index a8def16ce128d05bef01a09ac79b66ec7c4e555d..e7f256b2928dfbbdd35e48b2dbf5b1797eee727d 100644 --- a/feedbackloop.plan/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/plan/PlanImpl.java +++ b/feedbackloop.plan/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/plan/PlanImpl.java @@ -2,8 +2,9 @@ package de.tudresden.inf.st.eraser.feedbackloop.plan; import de.tudresden.inf.st.eraser.feedbackloop.api.Execute; import de.tudresden.inf.st.eraser.feedbackloop.api.Plan; -import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference; -import de.tudresden.inf.st.eraser.jastadd.model.Root; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.List; @@ -16,6 +17,7 @@ public class PlanImpl implements Plan { private Root knowledgeBase; private Execute execute; + private static final Logger logger = LogManager.getLogger(PlanImpl.class); @Override public void setKnowledgeBase(Root knowledgeBase) { @@ -33,9 +35,16 @@ public class PlanImpl implements Plan { } @Override - public void planToMatchPreferences(String activity) { - // TODO implement + public void planToMatchPreferences(Activity activity) { + logger.info("Plan got new activity [{}]: {}", activity.getIdentifier(), activity.getLabel()); List<ItemPreference> preferences = knowledgeBase.currentPreferences(); + knowledgeBase.getMachineLearningRoot().addChangeEvent(createRecognitionEvent(activity)); informExecute(preferences); } + + private ChangeEvent createRecognitionEvent(Activity activity) { + RecognitionEvent result = RecognitionEvent.createRecognitionEvent(knowledgeBase.getMachineLearningRoot().getActivityRecognition()); + result.setActivity(activity); + return result; + } } diff --git a/feedbackloop.plan/src/main/resources/log4j2.xml b/feedbackloop.plan/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/feedbackloop.plan/src/main/resources/log4j2.xml +++ b/feedbackloop.plan/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fb7ef980f26942c36accd5674b80128f6cf43abe..030ec1f8a437a05e86702c10632c66340d98ba0f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue May 07 14:40:56 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/influx_test/.gitignore b/influx_test/.gitignore index 84c048a73cc2e5dd24f807669eb99b0ce3123195..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/influx_test/.gitignore +++ b/influx_test/.gitignore @@ -1 +1,3 @@ /build/ +/bin/ +logs/ diff --git a/influx_test/build.gradle b/influx_test/build.gradle index fcb387f503c0960fee15af9ffbc5913d536bcb07..50fdeee4c477c0d43ad4ce73436630531a423a32 100644 --- a/influx_test/build.gradle +++ b/influx_test/build.gradle @@ -1,21 +1,8 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'org.influxdb:influxdb-java:2.14' - - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.influxdb', name: 'influxdb-java', version: '2.15' } run { diff --git a/influx_test/src/main/resources/log4j2.xml b/influx_test/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/influx_test/src/main/resources/log4j2.xml +++ b/influx_test/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/integration/.gitignore b/integration/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/integration/.gitignore +++ b/integration/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/integration/build.gradle b/integration/build.gradle index 47be63191b320dce8ea7c0673b15943a1496b634..219d9424d1b27114d05fc74ff704f6e7ce02937b 100644 --- a/integration/build.gradle +++ b/integration/build.gradle @@ -1,13 +1,5 @@ - -apply plugin: 'java' apply plugin: 'application' -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - run { mainClassName = 'de.tudresden.inf.st.eraser.integration.IntegrationMain' standardInput = System.in @@ -19,7 +11,4 @@ run { dependencies { compile project(':eraser-base') compile project(':openhab-mock') - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' } diff --git a/integration/src/main/java/de/tudresden/inf/st/eraser/integration/IntegrationMain.java b/integration/src/main/java/de/tudresden/inf/st/eraser/integration/IntegrationMain.java index a7ca2904072ea46e0a0243fd7bc47328729a55c4..3418238f3572595c4a9640369767a1d34d037920 100644 --- a/integration/src/main/java/de/tudresden/inf/st/eraser/integration/IntegrationMain.java +++ b/integration/src/main/java/de/tudresden/inf/st/eraser/integration/IntegrationMain.java @@ -79,17 +79,14 @@ public class IntegrationMain { logger.info("Start!"); Root model = Main.importFromFile(); // Root model = importFromLocalFile(); - logger.debug("Got model: {}", model.description()); + logger.debug("Got model: {}", model.getOpenHAB2Model().description()); MqttRoot mqttRoot = new MqttRoot(); mqttRoot.setHostByName("localhost"); mqttRoot.setIncomingPrefix("oh2/out/"); - MqttTopic irisTopic = new MqttTopic(); - irisTopic.setPart("iris1_item"); MqttTopic irisStateTopic = new MqttTopic(); - irisStateTopic.setPart("state"); - irisTopic.addSubTopic(irisStateTopic); + irisStateTopic.setTopicString("iris1_item/state"); Item iris = null; - for (Item item : model.items()) { + for (Item item : model.getOpenHAB2Model().items()) { if (item.getID().equals("iris1_item")) { iris = item; break; @@ -99,8 +96,7 @@ public class IntegrationMain { logger.error("Could not find iris1. Exiting"); return; } - irisStateTopic.setItem(iris); - mqttRoot.addTopic(irisTopic); + irisStateTopic.addItem(iris); model.setMqttRoot(mqttRoot); // JsonSerializer.write(model, "src/main/resources/openhab2-data.json"); JsonSerializer.write(model, "openhab2-data.json"); diff --git a/integration_test/.gitignore b/integration_test/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..54c5c0b024dab04da2a43d4135ed67ed2a670b46 --- /dev/null +++ b/integration_test/.gitignore @@ -0,0 +1,2 @@ +/build/ +logs/ diff --git a/integration_test/build.gradle b/integration_test/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..2d905f3f6d29a82267538d94376175d6556787d7 --- /dev/null +++ b/integration_test/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'application' + +dependencies { + testCompile project(':eraser-base') + testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.8' + testCompile group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.8' + testCompile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' +} + +run { + mainClassName = 'de.tudresden.inf.st.eraser.integration_test.Main' + standardInput = System.in + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} diff --git a/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/Main.java b/integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java similarity index 50% rename from feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/Main.java rename to integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java index df65366a4ff72363f97975383dca895555f6a8cb..c362161e99ef67c70a79278d93263cce1a91eb2f 100644 --- a/feedbackloop.analyze/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/analyze/Main.java +++ b/integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java @@ -1,6 +1,4 @@ -package de.tudresden.inf.st.eraser.feedbackloop.analyze; - -import de.tudresden.inf.st.eraser.jastadd.model.*; +package de.tudresden.inf.st.eraser.integration_test; public class Main { diff --git a/integration_test/src/main/resources/log4j2.xml b/integration_test/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c --- /dev/null +++ b/integration_test/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Console"> + <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> + </Console> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> + <Policies> + <OnStartupTriggeringPolicy/> + </Policies> + <DefaultRolloverStrategy max="20"/> + </RollingFile> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Root> + </Loggers> +</Configuration> diff --git a/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java b/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java new file mode 100644 index 0000000000000000000000000000000000000000..04c36a74dc0d172456410a925b9914cfce2a23a8 --- /dev/null +++ b/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java @@ -0,0 +1,150 @@ +package de.tudresden.inf.st.eraser.integration_test; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.tudresden.inf.st.eraser.openhab2.data.ItemData; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.fluent.Form; +import org.apache.http.client.fluent.Request; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.Arrays; +import java.util.function.Function; + +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Integration test to check openHAB-binding for eraser works well together with eraser itself. + * + * @author rschoene - Initial contribution + */ +@RunWith(Parameterized.class) +@Ignore +public class ItemTest { + + private static final String OPENHAB_ITEM_URI = "http://localhost:8080/rest/items/"; + private static final String ERASER_ITEM_URI = "http://localhost:4567/model/items/"; + + private static final String DIMMER_ITEM = "dimmer_item"; + private static final String NUMBER_ITEM = "number_item"; + private static final String SWITCH_ITEM = "switch_item"; + private static final String COLOR_ITEM = "color_item"; + + @BeforeClass + public static void ensureItemsAreCreated() throws IOException { + ensureItemCreated(DIMMER_ITEM, "Dimmer"); + ensureItemCreated(NUMBER_ITEM, "Number"); + ensureItemCreated(SWITCH_ITEM, "Switch"); + ensureItemCreated(COLOR_ITEM, "Color"); + } + + private static void ensureItemCreated(String name, String type) throws IOException { + // Create at openHAB + HttpResponse responseOpenHAB = Request.Put(OPENHAB_ITEM_URI + name) + .bodyForm(Form.form().add("type", type).add("name", name).build()) + .execute().returnResponse(); + assertThat( + responseOpenHAB.getStatusLine().getStatusCode(), + either(equalTo(HttpStatus.SC_CREATED)) + .or(equalTo(HttpStatus.SC_OK))); + + // Create at eraser + HttpResponse responseEraser = Request.Put(ERASER_ITEM_URI + name) + .bodyString(type + " Item: id=\"" + name + "\"", ContentType.TEXT_PLAIN) + .execute().returnResponse(); + assertThat( + responseEraser.getStatusLine().getStatusCode(), + equalTo(HttpStatus.SC_CREATED)); + } + + @Test + public void itemAvailable() throws IOException { + String name = "Tradfri_2_small_tv"; + ItemData itemData = Request.Get( OPENHAB_ITEM_URI + name ) + .execute().handleResponse( + response -> retrieveResourceFromResponse(response, ItemData.class)); + assertThat(itemData.type, equalTo("Dimmer")); + } + + @Test + public void dimmerSetStateAtOpenHAB() throws IOException { + String name = "Tradfri_2_small_tv"; + double newValue = 3.0; + // set item state with openHAB REST API + HttpResponse httpResponse = Request.Put(OPENHAB_ITEM_URI + name + "/state") + .bodyString(Double.toString(newValue), ContentType.TEXT_PLAIN) + .execute().returnResponse(); + assertThat( + httpResponse.getStatusLine().getStatusCode(), + equalTo(HttpStatus.SC_ACCEPTED)); + + // check whether state was set correctly + String responseOpenHAB = Request.Get(OPENHAB_ITEM_URI + name + "/state") + .execute().returnContent().asString(); + assertThat(Double.parseDouble(responseOpenHAB), equalTo(newValue)); + + // check whether state was updated on eraser side + String responseEraser = Request.Get(ERASER_ITEM_URI + name + "/state") + .execute().returnContent().asString(); + assertThat(Double.parseDouble(responseEraser), equalTo(newValue)); + } + + @Test + public void dimmerSetStateAtEraser() throws IOException { + String name = "Tradfri_2_small_tv"; + double newValue = 25.0; + // set item state with eraser REST API + String uri = ERASER_ITEM_URI + name + "/state"; + System.out.println(uri); + HttpResponse httpResponse = Request.Put(uri) + .bodyString(Double.toString(newValue), ContentType.TEXT_PLAIN) + .execute().returnResponse(); + assertThat( + httpResponse.getStatusLine().getStatusCode(), + equalTo(HttpStatus.SC_OK)); + + // check whether state was set correctly + String responseEraser = Request.Get(ERASER_ITEM_URI + name + "/state") + .execute().returnContent().asString(); + assertThat(Double.parseDouble(responseEraser), equalTo(newValue)); + + // check whether state was updated on openHAB side + String responseOpenHAB = Request.Get(OPENHAB_ITEM_URI + name + "/state") + .execute().returnContent().asString(); + assertThat(Double.parseDouble(responseOpenHAB), equalTo(newValue)); + } + + private static <T> T retrieveResourceFromResponse(HttpResponse response, Class<T> clazz) + throws IOException { + String jsonFromResponse = EntityUtils.toString(response.getEntity()); + ObjectMapper mapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper.readValue(jsonFromResponse, clazz); + } + + private static <T> Object[] d(String name, T initialValue, Function<T, String> toString, + Function<String, T> fromString) { + return new Object[]{name, initialValue, toString, fromString}; + } + + @Parameterized.Parameters(name= "{index}: {0}") + public static Iterable<Object[]> data() { + return Arrays.asList( + d(DIMMER_ITEM, 25.0, d -> Double.toString(d), Double::parseDouble), + d(NUMBER_ITEM, 4, i -> Integer.toString(i), Integer::parseInt), + d(SWITCH_ITEM, "ON", Function.identity(), Function.identity()), + d(COLOR_ITEM, "1,2,3", Function.identity(), Function.identity()) + ); + } + +} diff --git a/learner_test/.gitignore b/learner_test/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..84c048a73cc2e5dd24f807669eb99b0ce3123195 --- /dev/null +++ b/learner_test/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/learner_test/build.gradle b/learner_test/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..64ba25868ab02f58ed90c006f52cfb3e9563c411 --- /dev/null +++ b/learner_test/build.gradle @@ -0,0 +1,35 @@ +repositories { + mavenCentral() +} + +sourceCompatibility = 1.8 + +apply plugin: 'java' +apply plugin: 'application' + +dependencies { + compile project(':eraser-base') + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.8.1' + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '1.0.0.0' + + compile 'org.encog:encog-core:3.4' +} + +run { + mainClassName = 'de.tudresden.inf.st.eraser.learner_test.Main' + standardInput = System.in + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} diff --git a/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/Main.java b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..62f00cb624f4dbae09c84bca772d09318f5c9964 --- /dev/null +++ b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/Main.java @@ -0,0 +1,112 @@ +package de.tudresden.inf.st.eraser.learner_test; +import org.encog.Encog; +import org.encog.ml.MLClassification; +import org.encog.ml.data.MLData; +import org.encog.persist.EncogDirectoryPersistence; +import org.encog.util.csv.CSVFormat; +import org.encog.util.csv.ReadCSV; +import org.encog.util.simple.EncogUtility; + +import org.encog.ml.data.versatile.NormalizationHelper; +import org.encog.ml.data.versatile.VersatileMLDataSet; +import org.encog.ml.data.versatile.columns.ColumnDefinition; +import org.encog.ml.data.versatile.columns.ColumnType; +import org.encog.ml.data.versatile.sources.VersatileDataSource; +import org.encog.ml.data.versatile.sources.CSVDataSource; +import org.encog.ml.factory.MLMethodFactory; +import org.encog.ml.model.EncogModel; +import org.encog.ConsoleStatusReportable; +import org.encog.ml.MLRegression; +import java.io.File; +import java.util.Arrays; +import static org.encog.persist.EncogDirectoryPersistence.*; + +public class Main { + + public static void main(String[] args) { + //mapping the data into model + String savefile = "src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model.eg"; + String File = "src/main/java/de/tudresden/inf/st/eraser/learner_test/preference_data.csv"; + File file = new File(File); + VersatileDataSource source = new CSVDataSource(file, false, CSVFormat.DECIMAL_POINT); + VersatileMLDataSet data = new VersatileMLDataSet(source); + data.defineSourceColumn("monat", 0, ColumnType.continuous); + data.defineSourceColumn("day", 1, ColumnType.continuous); + data.defineSourceColumn("hour", 2, ColumnType.continuous); + data.defineSourceColumn("minute", 3, ColumnType.continuous); + ColumnDefinition outputColumn = data.defineSourceColumn("labels", 4, ColumnType.continuous); + data.defineSingleOutputOthersInput(outputColumn); + data.analyze(); + System.out.println("get data "); + EncogModel model = new EncogModel(data); + model.selectMethod(data, MLMethodFactory.TYPE_FEEDFORWARD); + //model.setReport(new ConsoleStatusReportable()); + data.normalize(); + NormalizationHelper helper = data.getNormHelper(); + System.out.println(helper.toString()); + model.holdBackValidation(0.3, true, 1001); + model.selectTrainingType(data); + MLRegression bestMethod = (MLRegression)model.crossvalidate(5, true); + MLClassification bestMethodtest=(MLClassification)model.crossvalidate(5,true); + /**System.out.println( "Training error: " + EncogUtility.calculateRegressionError(bestMethod, model.getTrainingDataset())); + System.out.println( "testTraining error: " + EncogUtility.calculateClassificationError(bestMethodtest, model.getTrainingDataset())); + System.out.println( "Validation error: " + EncogUtility.calculateRegressionError(bestMethod, model.getValidationDataset())); + System.out.println( "testValidation error: " + EncogUtility.calculateClassificationError(bestMethodtest, model.getValidationDataset())); + + System.out.println(helper.getClass()); + System.out.println(helper.toString()); + System.out.println("Final model: " + bestMethod); + System.out.println("Final testmodel: " + bestMethodtest);**/ + //NormalizationHelper helper = data.getNormHelper(); + + //test + String helperstr=helper.toString(); + String [] split=helperstr.split(";"); + String [] finalStr = split[split.length-1].replace("]","").replace("[",""). + split(","); + System.out.println(helper); + + // save network... + //to delete + saveObject(new File(savefile), bestMethodtest); + ReadCSV csv = new ReadCSV(File, false, CSVFormat.DECIMAL_POINT); + String[] line = new String[4]; + MLData input = helper.allocateInputVector(); + System.out.println("input test---------------"); + System.out.println(input); + while(csv.next()) { + StringBuilder result = new StringBuilder(); + line[0] = csv.get(0); + line[1] = csv.get(1); + line[2] = csv.get(2); + line[3] = csv.get(3); + String correct = csv.get(4); + helper.normalizeInputVector(line,input.getData(),false); + + MLData output = bestMethod.compute(input); + System.out.println("inputs:"); + System.out.println(input); + System.out.println("outputs:"); + System.out.println(output); + String brightnessChosen = helper.denormalizeOutputVectorToString(output)[0]; + + result.append(Arrays.toString(line)); + result.append(" -> predicted: "); + result.append(brightnessChosen); + result.append("(correct: "); + result.append(correct); + result.append(")"); + System.out.println(result.toString()); + break; + + } + // Delete data file and shut down. + //File.delete(); + Encog.getInstance().shutdown(); + /**Training error: 0.299928703107046 + testTraining error: 0.9931740614334471 + Validation error: 0.41277024952020763 + testValidation error: 0.992*/ + + } +} diff --git a/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/final_data.csv b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/final_data.csv new file mode 100644 index 0000000000000000000000000000000000000000..a1e263194d3ee4f95c64c3eff3b5123fb2246cf5 --- /dev/null +++ b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/final_data.csv @@ -0,0 +1,418 @@ +7,20,12,13,2 +7,20,14,40,1 +7,20,14,40,2 +7,21,13,2,2 +7,21,13,2,2 +7,21,14,23,2 +7,21,14,23,2 +7,21,15,41,2 +7,21,16,54,2 +7,21,16,54,2 +7,21,17,45,3 +7,22,12,28,3 +7,22,15,35,2 +7,22,15,35,2 +7,22,18,59,3 +7,22,18,59,3 +7,23,12,32,2 +7,23,12,32,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,23,16,7,2 +7,24,12,4,0 +7,24,12,4,0 +7,24,12,4,1 +7,24,14,38,2 +7,24,14,38,2 +7,24,18,54,3 +7,25,12,31,0 +7,25,12,32,1 +7,25,12,32,1 +7,25,15,6,3 +7,25,18,56,3 +7,26,13,41,2 +7,26,19,14,3 +7,27,11,39,2 +7,27,11,39,3 +7,27,11,46,3 +7,27,11,46,2 +7,27,13,8,2 +7,27,13,8,2 +7,27,13,9,2 +7,27,13,45,2 +7,27,13,45,2 +7,27,15,38,3 +7,28,12,12,2 +7,28,12,13,2 +7,28,12,41,2 +7,28,12,41,2 +7,28,12,41,2 +7,28,14,0,1 +7,28,14,0,2 +7,28,15,21,3 +7,28,18,56,3 +7,29,10,9,1 +7,29,10,9,1 +7,29,10,9,1 +7,29,11,54,0 +7,29,11,54,0 +7,29,11,54,0 +7,29,11,54,1 +7,29,14,10,2 +7,29,16,44,2 +7,29,16,44,2 +7,30,16,7,3 +7,30,18,45,3 +7,31,13,2,0 +7,31,13,2,1 +7,31,13,3,1 +7,31,13,3,1 +7,31,13,3,1 +7,31,18,39,3 +8,1,12,22,0 +8,1,12,22,1 +8,1,14,20,2 +8,1,14,20,2 +8,1,14,20,2 +8,1,15,55,3 +8,1,18,31,3 +8,1,18,37,3 +8,1,18,37,3 +8,1,19,2,3 +8,1,19,2,3 +8,1,20,5,3 +8,2,10,9,2 +8,2,10,9,1 +8,2,10,9,2 +8,2,10,9,2 +8,2,13,58,2 +8,2,13,58,2 +8,2,15,44,3 +8,2,15,44,3 +8,2,15,44,3 +8,2,17,21,3 +8,2,17,21,3 +8,2,17,21,3 +8,3,13,31,1 +8,3,13,31,2 +8,3,13,32,2 +8,3,16,43,3 +8,4,13,20,1 +8,4,13,20,2 +8,4,18,27,3 +8,5,13,37,2 +8,5,13,37,2 +8,5,18,33,3 +8,6,11,24,3 +8,6,11,24,3 +8,6,11,24,3 +8,6,13,50,3 +8,7,13,4,2 +8,7,13,4,2 +8,7,14,56,3 +8,8,12,13,2 +8,8,12,13,2 +8,8,15,51,2 +8,8,15,51,2 +8,8,15,51,3 +8,9,13,32,2 +8,9,13,32,2 +8,9,13,32,2 +8,9,15,8,2 +8,9,15,8,2 +8,9,15,8,2 +8,9,16,19,2 +8,10,11,32,0 +8,10,11,32,1 +8,10,11,32,1 +8,10,13,13,1 +8,10,13,13,1 +8,10,13,13,2 +8,10,16,42,3 +8,10,16,42,3 +8,11,14,6,2 +8,11,14,7,2 +8,11,18,54,3 +8,11,18,54,3 +8,11,18,54,3 +8,12,12,27,1 +8,12,12,27,1 +8,12,12,28,1 +8,12,13,53,2 +8,12,13,53,2 +8,12,13,53,2 +8,12,15,21,3 +8,13,13,16,1 +8,13,13,16,1 +8,13,13,16,1 +8,13,14,14,2 +8,13,14,14,2 +8,13,16,11,3 +8,13,17,18,3 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,1 +8,14,13,7,2 +8,14,13,7,2 +8,14,15,6,3 +8,15,14,5,2 +8,15,14,5,2 +8,15,14,6,2 +8,15,14,6,2 +8,15,16,41,3 +8,15,16,41,3 +8,15,17,30,3 +8,16,13,40,2 +8,16,13,40,2 +8,16,17,52,3 +8,16,17,53,3 +8,17,13,34,1 +8,17,13,35,2 +8,17,14,7,2 +8,17,19,2,3 +8,18,10,21,3 +8,18,11,14,2 +8,18,11,14,2 +8,18,11,14,2 +8,18,11,14,2 +8,18,14,25,2 +8,18,14,25,3 +8,18,14,25,2 +8,18,18,18,3 +8,18,18,19,3 +8,19,18,33,3 +8,19,18,33,3 +8,19,18,33,3 +8,19,18,33,3 +8,20,14,28,2 +8,20,14,28,2 +8,20,14,28,2 +8,20,14,28,2 +8,20,17,8,3 +8,20,18,22,3 +8,21,11,24,1 +8,21,11,24,1 +8,21,11,24,1 +8,21,15,34,3 +8,21,18,55,3 +8,22,12,3,1 +8,22,12,4,2 +8,22,12,4,2 +8,22,13,51,2 +8,22,13,51,2 +8,22,13,51,2 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,12,3 +8,22,18,40,3 +8,22,18,40,3 +8,23,13,42,1 +8,23,13,42,1 +8,23,17,32,3 +8,23,19,28,3 +8,23,20,27,3 +8,23,20,27,3 +8,23,21,49,3 +8,24,14,0,2 +8,24,14,0,2 +8,24,14,0,2 +8,24,14,0,2 +8,24,15,4,3 +8,24,15,4,3 +8,24,16,2,3 +8,24,16,3,3 +8,24,16,37,3 +8,24,17,9,3 +8,24,17,14,3 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,13,34,1 +8,25,15,1,3 +8,25,17,58,3 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,10,29,0 +8,26,16,42,3 +8,26,16,42,3 +8,26,18,41,3 +8,26,18,41,3 +8,27,13,41,2 +8,27,13,41,2 +8,27,13,41,2 +8,27,13,41,2 +8,27,17,42,3 +8,28,11,9,1 +8,28,11,9,1 +8,28,12,14,0 +8,28,12,14,1 +8,28,12,14,0 +8,28,15,3,2 +8,28,15,3,2 +8,28,16,31,3 +8,28,17,40,3 +8,29,14,44,3 +8,29,17,25,3 +8,30,12,5,0 +8,30,12,5,0 +8,30,12,5,0 +8,30,13,32,1 +8,30,13,32,1 +8,30,13,56,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,23,2 +8,30,14,41,2 +8,30,14,41,2 +8,30,14,41,2 +8,30,15,50,3 +8,30,17,0,3 +8,30,18,59,3 +8,30,18,59,3 +8,31,14,31,2 +8,31,14,31,2 +8,31,14,31,2 +8,31,17,59,3 +8,31,18,0,3 +9,1,16,13,3 +9,1,16,13,3 +9,1,16,13,3 +9,1,17,41,3 +9,2,13,44,1 +9,2,13,44,1 +9,2,13,44,1 +9,2,14,49,2 +9,2,14,49,2 +9,2,14,49,2 +9,2,16,6,3 +9,2,16,6,3 +9,2,17,2,3 +9,3,16,9,3 +9,3,17,35,3 +9,3,17,36,3 +9,4,12,57,1 +9,4,12,57,1 +9,4,15,8,3 +9,4,15,34,3 +9,4,16,26,3 +9,4,16,26,3 +9,4,18,37,3 +9,4,18,37,3 +9,4,18,37,3 +9,6,11,18,0 +9,6,11,18,0 +9,6,12,54,1 +9,6,12,54,1 +9,6,14,21,2 +9,6,14,21,2 +9,6,19,20,3 +9,7,11,50,0 +9,7,14,17,2 +9,7,14,57,3 +9,7,14,57,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,16,56,3 +9,7,18,38,3 +9,7,18,38,3 +9,8,11,4,2 +9,8,11,4,2 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,13,0 +9,8,11,14,0 +9,8,11,14,1 +9,8,11,14,1 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,0 +9,8,12,1,1 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,12,36,0 +9,8,13,37,1 +9,8,13,37,1 +9,8,13,37,1 +9,8,14,20,2 +9,8,14,20,2 +9,8,18,20,3 +9,9,12,47,1 +9,9,12,47,2 +9,9,12,47,2 +9,9,19,5,3 +9,10,13,15,1 +9,10,13,15,1 +9,10,13,15,0 +9,10,16,49,3 +9,10,19,6,3 +9,10,21,5,3 +9,11,14,16,2 +9,11,14,16,2 +9,11,14,16,2 +9,11,18,41,3 +9,12,14,43,2 +9,12,14,43,2 +9,12,14,43,2 +9,12,16,14,3 +9,12,17,12,3 +9,12,17,12,2 +9,12,17,12,3 +9,12,17,12,2 +9,12,20,44,3 +9,13,19,52,3 +9,14,14,39,2 +9,14,14,39,2 +9,14,15,14,3 +9,14,17,29,3 +9,14,17,29,3 +9,14,17,29,3 +9,15,11,41,1 +9,15,11,41,1 +9,15,13,4,1 +9,15,14,3,1 +9,15,14,3,2 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,36,1 +9,16,12,48,1 +9,16,12,48,1 +9,16,13,51,1 +9,16,13,51,2 +9,16,13,51,1 +9,16,15,13,3 +9,16,15,14,3 +9,16,15,14,3 +9,17,10,27,0 +9,17,10,27,0 +9,17,11,10,0 +9,17,11,10,0 +9,17,11,10,0 +9,17,12,43,1 +9,17,12,43,1 +9,17,12,43,1 +9,17,13,32,1 +9,17,13,32,1 +9,17,14,5,1 +9,17,14,5,2 +9,17,14,6,2 +9,17,15,7,3 +9,17,15,49,3 +9,17,15,49,3 +9,17,18,12,3 +9,17,18,13,3 diff --git a/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model.eg b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model.eg new file mode 100644 index 0000000000000000000000000000000000000000..47c37ff28ef95b7b22a2df05ec050c3ffeb53a30 --- /dev/null +++ b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model.eg @@ -0,0 +1,24 @@ +encog,BasicNetwork,java,3.4.0,1,1554196571101 +[BASIC] +[BASIC:PARAMS] +[BASIC:NETWORK] +beginTraining=0 +connectionLimit=0 +contextTargetOffset=0,0,0 +contextTargetSize=0,0,0 +endTraining=2 +hasContext=f +inputCount=4 +layerCounts=1,8,5 +layerFeedCounts=1,7,4 +layerContextCount=0,0,0 +layerIndex=0,1,9 +output=0.2537517424,0.3154675575,-0.8739039638,-0.4408848221,-0.8484433638,-0.999915299,-0.6964984771,-0.208278439,1,0,0,-0.4545454545,0.3559322034,1 +outputCount=1 +weightIndex=0,8,43 +weights=0.5976774048,-0.7925906525,0.7127327881,-0.9611660362,0.8031350986,-0.7286657218,1.0990482817,-0.5985785536,-0.0783115433,0.575612931,1.1267500918,1.7184744034,0.2271044512,-1.0525796764,0.0900869671,1.1492323512,0.6141715555,-1.0455927965,-0.0925453451,0.2471651431,2.3634316872,0.3939369257,0.4607437082,-0.1435186798,0.8428535365,-0.0848896791,-0.070602589,-1.2640263565,2.4899996734,-0.2185394776,10.3421332361,-0.1650898311,-0.2750133571,-0.79680959,-0.8051139953,0.8219933747,-0.0727160299,-0.4609522002,-1.0410685492,-0.5354063412,0.3028724456,-0.6835374219,0.169591233 +biasActivation=0,1,1 +[BASIC:ACTIVATION] +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationLinear" diff --git a/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model_test.eg b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model_test.eg new file mode 100644 index 0000000000000000000000000000000000000000..62fe6421d95e164aa14e123b950e831e37a5f23c --- /dev/null +++ b/learner_test/src/main/java/de/tudresden/inf/st/eraser/learner_test/save_model_test.eg @@ -0,0 +1,24 @@ +encog,BasicNetwork,java,3.4.0,1,1548158734516 +[BASIC] +[BASIC:PARAMS] +[BASIC:NETWORK] +beginTraining=0 +connectionLimit=0 +contextTargetOffset=0,0,0 +contextTargetSize=0,0,0 +endTraining=2 +hasContext=f +inputCount=4 +layerCounts=4,8,5 +layerFeedCounts=4,7,4 +layerContextCount=0,0,0 +layerIndex=0,4,12 +output=0.6991387348,-0.8711034513,-0.996886038,-0.832747291,-0.0935682806,-0.9996163977,0.5399150265,0.9411173394,-0.5084989975,0.4850010791,0.9999999957,1,0,-0.6666666667,-0.4545454545,0.6949152542,1 +outputCount=4 +weightIndex=0,32,67 +weights=-2.6901880743,0.6512821123,-1.2270002115,1.63124668,0.1982387305,-0.2994789552,1.5833040739,-0.9450411677,2.0541422847,-0.718279397,-1.1761952241,0.5028631512,0.0690323612,-1.496141565,-0.1955149568,-0.7453976822,-0.3691141073,0.9854755554,2.2113850088,-1.5216550292,0.9652087936,-1.3028209693,-1.3346156171,0.4142247818,1.0821207364,0.1987534858,0.6202881884,-0.2940331887,-1.4643282498,2.6960334656,-0.0167663298,-2.9907087565,0.3469960227,-0.0441249736,-2.5998575813,-0.7106361301,-0.8111809962,2.2216158678,-0.5482762437,-1.7996398291,-3.6734127565,-2.9102547958,0.4845401914,0.3760471288,-0.0124987546,0.3784047483,0.5860932613,-0.2682876707,0.7429004186,-7.559247176,-3.4421363532,1.1989747484,-2.3340717496,-1.4740773042,-0.7795788072,-1.8241693655,-0.630132295,-0.8191869009,-0.4060569987,-1.0997423162,-0.5495165849,0.1407829068,-2.2964930412,0.0798893221,-19.5271913755,2.0474187009,-0.2622671892 +biasActivation=0,1,1 +[BASIC:ACTIVATION] +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationTANH" +"org.encog.engine.network.activation.ActivationLinear" diff --git a/learner_test/src/main/resources/log4j2.xml b/learner_test/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..0594576fac98ba859e411597c90c8e3d989378bd --- /dev/null +++ b/learner_test/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Console"> + <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> + </Console> + <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" + filePattern="logs/jastadd-mquat-%i.log"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> + <Policies> + <OnStartupTriggeringPolicy/> + </Policies> + <DefaultRolloverStrategy max="20"/> + </RollingFile> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Root> + </Loggers> +</Configuration> diff --git a/make-new-project.py b/make-new-project.py index afd3a51931e3a7dc6823d47e1b9c97a65a2a2e76..635755567333e0d326ff346d5852d7d62437bbcb 100755 --- a/make-new-project.py +++ b/make-new-project.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python2.7 import argparse import os import shutil diff --git a/ml_test/.gitignore b/ml_test/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/ml_test/.gitignore +++ b/ml_test/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/ml_test/build.gradle b/ml_test/build.gradle index 241f853811b83bf6161c9e98efc0a22ec1ba64f2..a9e198eb94876643269b680a0de267e69724100f 100644 --- a/ml_test/build.gradle +++ b/ml_test/build.gradle @@ -1,19 +1,7 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/ml_test/src/main/java/de/tudresden/inf/st/eraser/ml_test/Main.java b/ml_test/src/main/java/de/tudresden/inf/st/eraser/ml_test/Main.java index 7a83aa0cc237f02ae018b3c3c4d73a4efce33d34..59c156853782b8e58010d24393e7c14c73ffefb8 100644 --- a/ml_test/src/main/java/de/tudresden/inf/st/eraser/ml_test/Main.java +++ b/ml_test/src/main/java/de/tudresden/inf/st/eraser/ml_test/Main.java @@ -23,22 +23,26 @@ public class Main { } private static Root createModel() { - Root model = Root.createEmptyRoot(); + Root root = Root.createEmptyRoot(); Group group = new Group(); group.setID("Group1"); - model.addGroup(group); + root.getOpenHAB2Model().addGroup(group); Item activityItem = newItem("activity", "Recognized activity", false, 8); Item brightnessItem = newItem("brightness", "Measured brightness", false, 5); group.addItem(activityItem); group.addItem(brightnessItem); - return model; + return root; } private static NumberItem newItem(String id, String label, boolean defaultSendState, int initialState) { NumberItem item = new NumberItem(); item.setID(id); item.setLabel(label); - item.setDefaultShouldSendState(defaultSendState); + if (defaultSendState) { + item.enableSendState(); + } else { + item.disableSendState(); + } item.setState(initialState); return item; } @@ -66,7 +70,7 @@ public class Main { } } logger.info("Classification results: {}", results); - logger.info("Took {}ms", String.join("ms, ", times.stream().map(l -> Long.toString(l)).collect(Collectors.toList()))); + logger.info("Took {}ms", times.stream().map(l -> Long.toString(l)).collect(Collectors.joining("ms, "))); logger.info("Took on average: {}ms", Arrays.stream(times.toArray(new Long[0])).mapToLong(l -> l).average().orElse(-1)); logger.info("Took on median: {}ms", @@ -74,30 +78,36 @@ public class Main { .skip((REPETITIONS-1)/2).limit(2-REPETITIONS%2).average().orElse(Double.NaN)); } - /** - * Purpose: Create a neural network with 3 layers (2 + 10 + 1 neurons) - * Sigmoid function for all layers, combinator of output is identity function - */ - private static void createAndTestBrightnessNetwork() { - /* - - Helligkeit NN: - - arbeitet momentan mit Zonen und nicht mit einzelnen Lampen - - 3 Layers - - Input Layer hat Neuronen (Aktivitätsnummer, Wert vom Helligkeitssensor) - - Hidden Layer hat 10 Neuronen - - Output Layer hat 1 Neuron ( Helligkeitswert) - - Aktivierungsfunktion: Sigmoidfunktion <- selbe für alle Layers - */ - Root model = createModel(); - Item activityItem = model.resolveItem("activity").orElseThrow( + private static class PreparationResult { + OutputLayer outputLayer; + DoubleArrayDoubleFunction sigmoid; + InputNeuron activity; + InputNeuron brightness; + NeuralNetworkRoot nn; + HiddenNeuron[] hiddenNeurons; + + PreparationResult(OutputLayer outputLayer, DoubleArrayDoubleFunction sigmoid, InputNeuron activity, + InputNeuron brightness, NeuralNetworkRoot nn, HiddenNeuron[] hiddenNeurons) { + this.outputLayer = outputLayer; + this.sigmoid = sigmoid; + this.activity = activity; + this.brightness = brightness; + this.nn = nn; + this.hiddenNeurons = hiddenNeurons; + } + } + + private static PreparationResult prepareNetwork() { + Root root = createModel(); + Item activityItem = root.getOpenHAB2Model().resolveItem("activity").orElseThrow( () -> new RuntimeException("Activity not found")); - Item brightnessItem = model.resolveItem("brightness").orElseThrow( + Item brightnessItem = root.getOpenHAB2Model().resolveItem("brightness").orElseThrow( () -> new RuntimeException("Brightness not found")); NeuralNetworkRoot nn = new NeuralNetworkRoot(); DoubleArrayDoubleFunction sigmoid = inputs -> Math.signum(Arrays.stream(inputs).sum()); - // input layer + // input layer (2 neurons) InputNeuron activity = new InputNeuron(); activity.setItem(activityItem); InputNeuron brightness = new InputNeuron(); @@ -105,31 +115,50 @@ public class Main { nn.addInputNeuron(activity); nn.addInputNeuron(brightness); - // output layer OutputLayer outputLayer = new OutputLayer(); - OutputNeuron output = new OutputNeuron(); - output.setLabel("Brightness_Output"); - output.setActivationFormula(sigmoid); - outputLayer.addOutputNeuron(output); - // we just have one output neuron, thus use IdentityFunction - outputLayer.setCombinator(inputs -> inputs[0]); nn.setOutputLayer(outputLayer); - // hidden layer + // hidden layer (10 neurons) HiddenNeuron[] hiddenNeurons = new HiddenNeuron[10]; - for (int i = 0; i < hiddenNeurons.length; i++) { + for (int hiddenIndex = 0; hiddenIndex < hiddenNeurons.length; hiddenIndex++) { HiddenNeuron hiddenNeuron = new HiddenNeuron(); hiddenNeuron.setActivationFormula(sigmoid); - hiddenNeurons[i] = hiddenNeuron; nn.addHiddenNeuron(hiddenNeuron); activity.connectTo(hiddenNeuron, 1.0/2.0); brightness.connectTo(hiddenNeuron, 1.0/2.0); - hiddenNeuron.connectTo(output, 1.0/hiddenNeurons.length); } + root.getMachineLearningRoot().setPreferenceLearning(nn); + + return new PreparationResult(outputLayer, sigmoid, activity, brightness, nn, hiddenNeurons); + } + + /** + * Purpose: Create a neural network with 3 layers (2 + 10 + 1 neurons) + * Sigmoid function for all layers, combinator of output is identity function + */ + private static void createAndTestBrightnessNetwork() { + /* + - Helligkeit NN: + - arbeitet momentan mit Zonen und nicht mit einzelnen Lampen + - 3 Layers + - Input Layer hat Neuronen (Aktivitätsnummer, Wert vom Helligkeitssensor) + - Hidden Layer hat 10 Neuronen + - Output Layer hat 1 Neuron ( Helligkeitswert) + - Aktivierungsfunktion: Sigmoidfunktion <- selbe für alle Layers + */ + PreparationResult pr = prepareNetwork(); + OutputNeuron output = new OutputNeuron(); + output.setLabel("Brightness_Output"); + output.setActivationFormula(pr.sigmoid); + pr.outputLayer.addOutputNeuron(output); + // we just have one output neuron, thus use IdentityFunction + pr.outputLayer.setCombinator(inputs -> inputs[0]); - model.getMachineLearningRoot().setPreferenceLearning(nn); + for (HiddenNeuron hiddenNeuron : pr.hiddenNeurons) { + hiddenNeuron.connectTo(output, 1.0/pr.hiddenNeurons.length); + } - classifyTimed(nn, NeuralNetworkRoot::classify, + classifyTimed(pr.nn, NeuralNetworkRoot::classify, classification -> Double.toString(classification.number)); } @@ -138,52 +167,23 @@ public class Main { * Sigmoid function for all layers, combinator creates RGB value in hex form */ private static void createAndTestColorNetwork() { - Root model = createModel(); - Item activityItem = model.resolveItem("activity").orElseThrow( - () -> new RuntimeException("Activity not found")); - Item brightnessItem = model.resolveItem("brightness").orElseThrow( - () -> new RuntimeException("Brightness not found")); - NeuralNetworkRoot nn = new NeuralNetworkRoot(); - - DoubleArrayDoubleFunction sigmoid = inputs -> Math.signum(Arrays.stream(inputs).sum()); - - // input layer (2 neurons) - InputNeuron activity = new InputNeuron(); - activity.setItem(activityItem); - InputNeuron brightness = new InputNeuron(); - brightness.setItem(brightnessItem); - nn.addInputNeuron(activity); - nn.addInputNeuron(brightness); - - // output layer (3 neurons) - OutputLayer outputLayer = new OutputLayer(); + PreparationResult pr = prepareNetwork(); for (int i = 0; i < 3; i++) { OutputNeuron output = new OutputNeuron(); output.setLabel("Brightness_Output_" + i); output.setActivationFormula(inputs -> Arrays.stream(inputs).sum()); - outputLayer.addOutputNeuron(output); + pr.outputLayer.addOutputNeuron(output); } // we have three output neurons, combine them to a double value (representing RGB) - outputLayer.setCombinator(inputs -> 65536 * Math.ceil(255.0 * inputs[0]) + 256 * Math.ceil(255.0 * inputs[1]) + Math.ceil(255.0 * inputs[0])); - nn.setOutputLayer(outputLayer); + pr.outputLayer.setCombinator(inputs -> 65536 * Math.ceil(255.0 * inputs[0]) + 256 * Math.ceil(255.0 * inputs[1]) + Math.ceil(255.0 * inputs[0])); - // hidden layer (10 neurons) - HiddenNeuron[] hiddenNeurons = new HiddenNeuron[10]; - for (int hiddenIndex = 0; hiddenIndex < hiddenNeurons.length; hiddenIndex++) { - HiddenNeuron hiddenNeuron = new HiddenNeuron(); - hiddenNeuron.setActivationFormula(sigmoid); - hiddenNeurons[hiddenIndex] = hiddenNeuron; - nn.addHiddenNeuron(hiddenNeuron); - activity.connectTo(hiddenNeuron, 1.0/2.0); - brightness.connectTo(hiddenNeuron, 1.0/2.0); - for (int outputIndex = 0; outputIndex < outputLayer.getNumOutputNeuron(); outputIndex++) { - hiddenNeuron.connectTo(outputLayer.getOutputNeuron(outputIndex), random.nextDouble() * 1.0/hiddenNeurons.length); + for (HiddenNeuron hiddenNeuron : pr.hiddenNeurons) { + for (int outputIndex = 0; outputIndex < pr.outputLayer.getNumOutputNeuron(); outputIndex++) { + hiddenNeuron.connectTo(pr.outputLayer.getOutputNeuron(outputIndex), random.nextDouble() * 1.0/pr.hiddenNeurons.length); } } - model.getMachineLearningRoot().setPreferenceLearning(nn); - - classifyTimed(nn, NeuralNetworkRoot::classify, + classifyTimed(pr.nn, NeuralNetworkRoot::classify, classification -> Double.toHexString(classification.number)); // long before = System.nanoTime(); diff --git a/ml_test/src/main/resources/log4j2.xml b/ml_test/src/main/resources/log4j2.xml index a5132e32502bf95c14ac0e6e50c2185325643ca7..686c2a889038bd7e7d89928939edfd09a5f15a94 100644 --- a/ml_test/src/main/resources/log4j2.xml +++ b/ml_test/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/ml_test_boqi/.gitignore b/ml_test_boqi/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 --- /dev/null +++ b/ml_test_boqi/.gitignore @@ -0,0 +1,3 @@ +/build/ +/bin/ +logs/ diff --git a/ml_test_boqi/build.gradle b/ml_test_boqi/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..d07b5930b7d3c75216b36bb9f5d9fa08b5d9d2c0 --- /dev/null +++ b/ml_test_boqi/build.gradle @@ -0,0 +1,33 @@ +repositories { + mavenCentral() +} + +sourceCompatibility = 1.8 + +apply plugin: 'java' +apply plugin: 'application' + +dependencies { + compile project(':eraser-base') + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' +} + +run { + mainClassName = 'de.tudresden.inf.st.eraser.ml_test_boqi.Main' + standardInput = System.in + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} diff --git a/ml_test_boqi/src/main/java/de/tudresden/inf/st/eraser/ml_test_boqi/Main.java b/ml_test_boqi/src/main/java/de/tudresden/inf/st/eraser/ml_test_boqi/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..f42663472e3a986c82df86e4bf89d0f9ff706441 --- /dev/null +++ b/ml_test_boqi/src/main/java/de/tudresden/inf/st/eraser/ml_test_boqi/Main.java @@ -0,0 +1,219 @@ +package de.tudresden.inf.st.eraser.ml_test_boqi; + +import de.tudresden.inf.st.eraser.jastadd.model.*; +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.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.math3.stat.StatUtils; +import de.tudresden.inf.st.eraser.jastadd.model.Item; + + +public class Main { + + private static final Logger logger = LogManager.getLogger(Main.class); + + public static void main(String[] args) { + + logger.info("Hello World!"); + createAndTestBrightnessNetwork(); + } + private static Root createModel() { + Root model = Root.createEmptyRoot(); + Group group = new Group(); + group.setID("Group1"); + model.addGroup(group); + + // inputs items muss normalize 1.0, 0.06666666666666665, 0.4545454545454546, -0.5593220338983051, 1(bias) + + NumberItem monthItem = new NumberItem(); + monthItem.setState(-1.0); + monthItem.setID("month"); + monthItem.setLabel("datetime-month"); + + NumberItem dayItem = new NumberItem(); + dayItem.setState(0.2666666666666666); + dayItem.setID("day"); + dayItem.setLabel("datetime-day"); + + NumberItem hourItem = new NumberItem(); + hourItem.setState(-0.6363636363636364); + hourItem.setID("hour"); + hourItem.setLabel("datetime-hour"); + + NumberItem minuteItem = new NumberItem(); + minuteItem.setState(-0.5593220338983051); + minuteItem.setID("minute"); + minuteItem.setLabel("datetime-minute"); + + NumberItem biasItem = new NumberItem(); + biasItem.setState(1); + biasItem.setID("bias"); + biasItem.setLabel("bias"); + + group.addItem(monthItem); + group.addItem(dayItem); + group.addItem(hourItem); + group.addItem(minuteItem); + group.addItem(biasItem); + return model; + } + private static final int REPETITIONS = 1; + private static void classifyTimed( + NeuralNetworkRoot nn, + Function<NeuralNetworkRoot, DoubleNumber> classify, + Function<DoubleNumber, String> leafToString) { + List<String> results = new ArrayList<>(); + List<Long> times = new ArrayList<>(); + long before = System.nanoTime(); + DoubleNumber classification = classify.apply(nn); + long diff = System.nanoTime() - before; + results.add(leafToString.apply(classification)); + times.add(TimeUnit.NANOSECONDS.toMillis(diff)); + logger.info("Classification results: {}", results); + logger.info("Took {}ms", String.join("ms, ", times.stream().map(l -> Long.toString(l)).collect(Collectors.toList()))); + logger.info("Took on average: {}ms", + Arrays.stream(times.toArray(new Long[0])).mapToLong(l -> l).average().orElse(-1)); + logger.info("Took on median: {}ms", + Arrays.stream(times.toArray(new Long[0])).mapToLong(l -> l).sorted() + .skip((REPETITIONS - 1) / 2).limit(2 - REPETITIONS % 2).average().orElse(Double.NaN)); + } + + /** + * Purpose: Create a neural network with 3 layers (5 + 8 + 4 neurons) + */ + private static void createAndTestBrightnessNetwork() { + Root model = createModel(); + Item monthItem = model.resolveItem("month").orElseThrow( + () -> new RuntimeException("Month not found")); + Item dayItem = model.resolveItem("day").orElseThrow( + () -> new RuntimeException("Day not found")); + Item hourItem = model.resolveItem("hour").orElseThrow( + () -> new RuntimeException("Hour not found")); + Item minuteItem = model.resolveItem("minute").orElseThrow( + () -> new RuntimeException("Minute not found")); + Item biasItem = model.resolveItem("bias").orElseThrow( + () -> new RuntimeException("Bias not found")); + + NeuralNetworkRoot nn = new NeuralNetworkRoot(); + + DoubleArrayDoubleFunction sigmoid = inputs -> Math.signum(Arrays.stream(inputs).sum()); + DoubleArrayDoubleFunction tanh= inputs ->Math.tanh(Arrays.stream(inputs).sum()); + DoubleArrayDoubleFunction function_one= inputs->function_one(); + + //Weights outputs from learner Module + ArrayList<Double> weights= new ArrayList<Double>(Arrays.asList( + -4.8288886204,0.6723236931,2.1451097188,-0.8551053267,-0.7858304445,4.1369566727,-3.3096691918, + -0.2190980261,2.6871317298,1.2272772167,-2.5292510941,-1.2860407542,-4.2280191541,1.004752063, + 0.8345207039,0.0123185817,-0.5921808915,0.0967336988,-0.305892589,0.5572392781,-0.7190098073, + -1.6247354373,0.4589248822,-0.0269816271,2.2208040852,-3.6281085698,0.2204999381,4.7263701556, + -4.8348948698,0.231141867,8.7120706018,-1.4912707741,0.9482851705,0.1377551973,-6.6525856465, + -1.321197315,-2.7369948929,17.664289214,-3.1279212743,-0.8245974167,-1.4251924355,0.8370511414, + 2.0841638143,-0.210152817,-1.9414132298,-1.7973688846,-2.1977997794,-3.6046836685,-3.3403186721, + -6.1556924635,-2.8952903587,-1.0773989561,0.2300429028,-0.2184650371,0.0297181797,0.5709092417, + 1.3960358442,-3.1577981239,0.0423944625,-17.8143314027,-1.4439317172,-0.5137688896,1.0166045804, + 0.3059149818,1.0938282764,0.6203368549,0.702449827)); + // input layer + InputNeuron month = new InputNeuron(); + month.setItem(monthItem); + InputNeuron day = new InputNeuron(); + day.setItem(dayItem); + InputNeuron hour = new InputNeuron(); + hour.setItem(hourItem); + InputNeuron minute = new InputNeuron(); + minute.setItem(minuteItem); + InputNeuron bias = new InputNeuron(); + bias.setItem(biasItem); + + nn.addInputNeuron(month); + nn.addInputNeuron(day); + nn.addInputNeuron(hour); + nn.addInputNeuron(minute); + nn.addInputNeuron(bias); + + // output layer + OutputLayer outputLayer = new OutputLayer(); + OutputNeuron output0 = new OutputNeuron(); + output0.setActivationFormula(tanh); + OutputNeuron output1 = new OutputNeuron(); + output1.setActivationFormula(tanh); + OutputNeuron output2 = new OutputNeuron(); + output2.setActivationFormula(tanh); + OutputNeuron output3 = new OutputNeuron(); + output3.setActivationFormula(tanh); + + outputLayer.addOutputNeuron(output0); + outputLayer.addOutputNeuron(output1); + outputLayer.addOutputNeuron(output2); + outputLayer.addOutputNeuron(output3); + + outputLayer.setCombinator(inputs->predictor(inputs)); + nn.setOutputLayer(outputLayer); + + // hidden layer + HiddenNeuron[] hiddenNeurons = new HiddenNeuron[8]; + for (int i = 0; i < (hiddenNeurons.length); i++) { + + if (i==7){ + HiddenNeuron hiddenNeuron = new HiddenNeuron(); + hiddenNeuron.setActivationFormula(function_one); + hiddenNeurons[i] = hiddenNeuron; + nn.addHiddenNeuron(hiddenNeuron); + bias.connectTo(hiddenNeuron,1.0); + hiddenNeuron.connectTo(output0, weights.get(i)); + hiddenNeuron.connectTo(output1, weights.get(i+8)); + hiddenNeuron.connectTo(output2, weights.get(i+8*2)); + hiddenNeuron.connectTo(output3, weights.get(i+8*3)); + } + else{ + HiddenNeuron hiddenNeuron = new HiddenNeuron(); + hiddenNeuron.setActivationFormula(tanh); + hiddenNeurons[i] = hiddenNeuron; + nn.addHiddenNeuron(hiddenNeuron); + + month.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length*4)+i*5)); + day.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length*4+1)+i*5)); + hour.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length*4+2)+i*5)); + minute.connectTo(hiddenNeuron, weights.get((hiddenNeurons.length*4+3)+i*5)); + bias.connectTo(hiddenNeuron,weights.get((hiddenNeurons.length*4+4)+i*5)); + hiddenNeuron.connectTo(output0, weights.get(i)); + hiddenNeuron.connectTo(output1, weights.get(i+8)); + hiddenNeuron.connectTo(output2, weights.get(i+8*2)); + hiddenNeuron.connectTo(output3, weights.get(i+8*3));} + } + + model.getMachineLearningRoot().setPreferenceLearning(nn); + System.out.println(model.prettyPrint()); + + classifyTimed(nn, NeuralNetworkRoot::classify, + classification -> Double.toString(classification.number)); + } + private static double function_one() { + return 1.0; + } + private static double predictor(double[] inputs) { + int index=0; + double maxinput=StatUtils.max(inputs); + System.out.println(inputs); + for (int i = 0; i < inputs.length; i++) + { + if (inputs[i] == maxinput){ + index=i; + } + } + //outputs from learner + ArrayList<Double> outputs= new ArrayList<Double>(Arrays.asList(2.0,1.0,3.0,0.0)); + double output=outputs.get(index); + return output; + } +} + +//inputs: +//[BasicMLData:-1.0,0.2666666666666666,-0.6363636363636364,-0.5593220338983051] +//outputs: +//[BasicMLData:-0.9151867668336432,-0.1568555041251098,-0.9786996639280675,-0.9436628188408074] +//[7, 20, 12, 13] -> predicted: 1(correct: 2) \ No newline at end of file diff --git a/ml_test_boqi/src/main/resources/log4j2.xml b/ml_test_boqi/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..0594576fac98ba859e411597c90c8e3d989378bd --- /dev/null +++ b/ml_test_boqi/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <Console name="Console"> + <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> + </Console> + <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" + filePattern="logs/jastadd-mquat-%i.log"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> + <Policies> + <OnStartupTriggeringPolicy/> + </Policies> + <DefaultRolloverStrategy max="20"/> + </RollingFile> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + <AppenderRef ref="RollingFile"/> + </Root> + </Loggers> +</Configuration> diff --git a/openhab-mock/.gitignore b/openhab-mock/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/openhab-mock/.gitignore +++ b/openhab-mock/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/openhab-mock/build.gradle b/openhab-mock/build.gradle index 94017c3d7308df49f404a1c49203b807fa4a5ef7..d4197d59aacaebd8d31ff349c2ed78007f0bfb2d 100644 --- a/openhab-mock/build.gradle +++ b/openhab-mock/build.gradle @@ -1,14 +1,6 @@ -apply plugin: 'java' apply plugin: 'application' -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - run { -// mainClassName = 'de.tudresden.inf.st.eraser.openhab_mock.MockMain' mainClassName = 'de.tudresden.inf.st.eraser.openhab_mock.HueMain' standardInput = System.in if (project.hasProperty("appArgs")) { @@ -19,9 +11,5 @@ run { dependencies { compile project(':eraser-base') compile project(':commons.color') - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - compile 'com.opencsv:opencsv:3.8' - compile 'org.apache.commons:commons-math3:3.6.1' - testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'com.opencsv', name: 'opencsv', version: '3.8' } diff --git a/openhab-mock/src/main/resources/log4j2.xml b/openhab-mock/src/main/resources/log4j2.xml index 89799a2f09ba34d288e610d960b3ed6348213105..18175a02521156259c8789745fb849fa893302e9 100644 --- a/openhab-mock/src/main/resources/log4j2.xml +++ b/openhab-mock/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/org.openhab.action.machinelearn/.gitignore b/org.openhab.action.machinelearn/.gitignore index 6ecea694e9b18e4e42bed81e7e4d44bed9301fb7..64c2056c6f34fbf226e041a575bdd73dff75df1f 100644 --- a/org.openhab.action.machinelearn/.gitignore +++ b/org.openhab.action.machinelearn/.gitignore @@ -1,2 +1,3 @@ build/ /bin/ +logs/ diff --git a/org.openhab.action.machinelearn/build.gradle b/org.openhab.action.machinelearn/build.gradle index 12d6d1e875fee2da8bb334ebcf14e95d9e3c66af..7360d4c8b932a5805e9bd29f21960d3c67b9851e 100644 --- a/org.openhab.action.machinelearn/build.gradle +++ b/org.openhab.action.machinelearn/build.gradle @@ -1,35 +1,12 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' -//apply plugin: 'application' - dependencies { compile files('lib/weka.jar') compile project(':stub.org.openhab.core.scriptengine.action') -// compile 'org.apache.commons:commons-lang3:3.8.1' -// compile group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.0.0' -// compile 'org.openhab.core.library.types' -// compile 'org.openhab.core.scriptengine.action' compile group: 'org.osgi', name: 'org.osgi.framework', version: '1.9.0' compile group: 'org.osgi', name: 'org.osgi.service.cm', version: '1.6.0' compile group: 'org.osgi', name: 'org.osgi.service.component', version: '1.4.0' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } -//run { -// mainClassName = 'de.tudresden.inf.st.eraser.skywriter_hue_integration.Main' -// standardInput = System.in -// if (project.hasProperty("appArgs")) { -// args Eval.me(appArgs) -// } -//} - sourceSets { main { java { diff --git a/org.openlicht.action.reinforcementlearning/.gitignore b/org.openlicht.action.reinforcementlearning/.gitignore index 6ecea694e9b18e4e42bed81e7e4d44bed9301fb7..64c2056c6f34fbf226e041a575bdd73dff75df1f 100644 --- a/org.openlicht.action.reinforcementlearning/.gitignore +++ b/org.openlicht.action.reinforcementlearning/.gitignore @@ -1,2 +1,3 @@ build/ /bin/ +logs/ diff --git a/org.openlicht.action.reinforcementlearning/build.gradle b/org.openlicht.action.reinforcementlearning/build.gradle index 54e1daf916b41766a9c4dc961e365040744ff608..572c87ea95295ba93b68e1f8bae5bed2dcc0149d 100644 --- a/org.openlicht.action.reinforcementlearning/build.gradle +++ b/org.openlicht.action.reinforcementlearning/build.gradle @@ -1,25 +1,11 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' -//apply plugin: 'application' - dependencies { compile files('lib/encog-core-3.4.jar') compile project(':stub.org.openhab.core.scriptengine.action') - compile 'org.apache.commons:commons-lang3:3.8.1' compile group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.200' -// compile 'org.openhab.core.library.types' -// compile 'org.openhab.core.scriptengine.action' compile group: 'org.osgi', name: 'org.osgi.framework', version: '1.9.0' compile group: 'org.osgi', name: 'org.osgi.service.cm', version: '1.6.0' compile group: 'org.osgi', name: 'org.osgi.service.component', version: '1.4.0' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } sourceSets { diff --git a/project-template/.gitignore b/project-template/.gitignore index 84c048a73cc2e5dd24f807669eb99b0ce3123195..54c5c0b024dab04da2a43d4135ed67ed2a670b46 100644 --- a/project-template/.gitignore +++ b/project-template/.gitignore @@ -1 +1,2 @@ /build/ +logs/ diff --git a/project-template/build.gradle b/project-template/build.gradle index 9cb094bf6e4b76641f50f823f9f526b8c8ba7657..3bae16225ca01a6fea919d36be3a383c6a62507f 100644 --- a/project-template/build.gradle +++ b/project-template/build.gradle @@ -1,19 +1,7 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/project-template/src/main/resources/log4j2.xml b/project-template/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/project-template/src/main/resources/log4j2.xml +++ b/project-template/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/project-template/src/test/java/de/tudresden/inf/st/eraser/projectName/ATest.java b/project-template/src/test/java/de/tudresden/inf/st/eraser/projectName/ATest.java index 59db3af92a329827411cb89a18dc3ea4ab833020..50f834da9ece7f0ba9ba3ed51c2adedbfbe318eb 100644 --- a/project-template/src/test/java/de/tudresden/inf/st/eraser/projectName/ATest.java +++ b/project-template/src/test/java/de/tudresden/inf/st/eraser/projectName/ATest.java @@ -17,6 +17,6 @@ public class ATest { @Test public void test1() { - fail(); + } } diff --git a/ragdoc-view b/ragdoc-view new file mode 160000 index 0000000000000000000000000000000000000000..4bb8afb1bc3dc6b346e04e72c5896568b2e9b192 --- /dev/null +++ b/ragdoc-view @@ -0,0 +1 @@ +Subproject commit 4bb8afb1bc3dc6b346e04e72c5896568b2e9b192 diff --git a/settings.gradle b/settings.gradle index 44b66ee077cceb9a26f2cfed97b319d4a30741a3..0a118ec458b406dfae388bf1218c2abd4bfd356b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ rootProject.name = 'eraser' include ':eraser-base' include 'openhab-mock' include 'integration' +include ':benchmark' include ':commons.color' include ':skywriter-hue-integration' include ':org.openhab.action.machinelearn' @@ -19,3 +20,5 @@ include ':feedbackloop.learner' include ':influx_test' include ':eraser.spark' include ':eraser.starter' +include ':feedbackloop.learner_backup' +include ':datasets' diff --git a/skywriter-hue-integration/.gitignore b/skywriter-hue-integration/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/skywriter-hue-integration/.gitignore +++ b/skywriter-hue-integration/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/skywriter-hue-integration/build.gradle b/skywriter-hue-integration/build.gradle index ab64e3d71e5b0b3c4e5939b2ffa387718e05a242..410a39b582bd8ea9316289b95c1f7f0083de6576 100644 --- a/skywriter-hue-integration/build.gradle +++ b/skywriter-hue-integration/build.gradle @@ -1,20 +1,8 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' apply plugin: 'application' dependencies { compile project(':eraser-base') compile project(':commons.color') - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8' - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' } run { diff --git a/skywriter-hue-integration/src/main/java/de/tudresden/inf/st/eraser/skywriter_hue_integration/Main.java b/skywriter-hue-integration/src/main/java/de/tudresden/inf/st/eraser/skywriter_hue_integration/Main.java index 19d08a1372d4bff1d308f036b50c64fe05106346..3f64c8cdd3c530d8a287a0d3ce0d140f449c5921 100644 --- a/skywriter-hue-integration/src/main/java/de/tudresden/inf/st/eraser/skywriter_hue_integration/Main.java +++ b/skywriter-hue-integration/src/main/java/de/tudresden/inf/st/eraser/skywriter_hue_integration/Main.java @@ -3,9 +3,7 @@ package de.tudresden.inf.st.eraser.skywriter_hue_integration; import beaver.Parser; import de.tudresden.inf.st.eraser.commons.color.ColorUtils; import de.tudresden.inf.st.eraser.commons.color.ColorUtils.RGBvalues; -import de.tudresden.inf.st.eraser.jastadd.model.Item; -import de.tudresden.inf.st.eraser.jastadd.model.Root; -import de.tudresden.inf.st.eraser.jastadd.model.Rule; +import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.openhab2.mqtt.MQTTUpdater; import de.tudresden.inf.st.eraser.util.ParserUtils; import org.apache.logging.log4j.LogManager; @@ -38,7 +36,8 @@ public class Main { @SuppressWarnings("ResultOfMethodCallIgnored") public static void main(String[] args) throws IOException, Parser.Exception { // use openHAB-eraser-connection to update hue (automatically done) - Root model = ParserUtils.load("skywriter-hue.eraser", Main.class); + Root root = ParserUtils.load("skywriter-hue.eraser", Main.class); + OpenHAB2Model model = root.getOpenHAB2Model(); Item irisItem = model.resolveItem("iris1_item").orElseThrow(() -> new NoSuchElementException("Iris1_item not found")); Item skywriter1_x = model.resolveItem("skywriter1_x").orElseThrow(() -> @@ -47,20 +46,23 @@ public class Main { new NoSuchElementException("Skywriter1 y not found")); Item skywriter1_xyz = model.resolveItem("skywriter1_xyz").orElseThrow(() -> new NoSuchElementException("Skywriter1 xyz not found")); - System.out.println(model.prettyPrint()); + System.out.println(root.prettyPrint()); // define rule to switch color, based on xyz-to-rgb-to-hsb mapping Rule mapXYtoIrisState = new Rule(); - model.addRule(mapXYtoIrisState); - mapXYtoIrisState.addEventFor(skywriter1_xyz, "x,y, or z changed"); + root.addRule(mapXYtoIrisState); + mapXYtoIrisState.activateFor(skywriter1_xyz); // mapXYtoIrisState.addEventFor(skywriter1_y, "y changed"); - mapXYtoIrisState.setCondition(root -> true); // always fire - mapXYtoIrisState.setAction(root -> updateStateRGB(skywriter1_xyz, irisItem)); + SetStateFromItemsAction action = new SetStateFromItemsAction(); + action.addSourceItem(skywriter1_xyz); + action.setCombinator(items -> updateStateRGB(items.iterator().next())); + action.setAffectedItem(irisItem); + mapXYtoIrisState.addAction(action); // get mqtt message directly from skywriter, not via binding (which is not working atm) Lock abortLock = new ReentrantLock(); Condition abortCondition = abortLock.newCondition(); Thread readFromOpenHABThread = new Thread(() -> { - try (MQTTUpdater updater = new MQTTUpdater(model)) { + try (MQTTUpdater updater = new MQTTUpdater(root)) { updater.start(); if (!updater.waitUntilReady(3, TimeUnit.SECONDS)) { logger.error("openHAB reader not ready. Aborting."); @@ -80,7 +82,7 @@ public class Main { } }); if (USE_READ_FROM_OPENHAB || SEND_TO_OPENHAB) { - model.getMqttRoot().setHostByName(MQTT_HOST); + root.getMqttRoot().setHostByName(MQTT_HOST); } if (USE_READ_FROM_OPENHAB) { readFromOpenHABThread.start(); @@ -120,7 +122,7 @@ public class Main { } // wait for user to press enter - System.out.println("Press [Enter] to quit updating the model."); + System.out.println("Press [Enter] to quit updating the root."); System.in.read(); // and then cancel the threads @@ -152,7 +154,7 @@ public class Main { irisItem.setStateFromString(String.format("%d,%d,%d", hsb.hue, hsb.saturation, hsb.brightness)); } - private static void updateStateRGB(Item skywriter1_xyz, Item irisItem) { + private static String updateStateRGB(Item skywriter1_xyz) { String[] tokens = skywriter1_xyz.getStateAsString().split(","); int[] rgbArray = new int[3]; for (int i = 0; i < 3; i++) { @@ -161,6 +163,6 @@ public class Main { } ColorUtils.HSBvalues255 hsb = ColorUtils.convertRGBtoHSB(RGBvalues.of(rgbArray)) .toIntegral().ensureBounds(); - irisItem.setStateFromString(String.format("%d,%d,%d", hsb.hue, hsb.saturation, hsb.brightness)); + return String.format("%d,%d,%d", hsb.hue, hsb.saturation, hsb.brightness); } } diff --git a/skywriter-hue-integration/src/main/resources/log4j2.xml b/skywriter-hue-integration/src/main/resources/log4j2.xml index 5c534092d64e9c1834c2ba20208c057e2b56be16..5d1091ea995c881e5985a0cfc925a952ff50d0bc 100644 --- a/skywriter-hue-integration/src/main/resources/log4j2.xml +++ b/skywriter-hue-integration/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/> diff --git a/skywriter-hue-integration/src/main/resources/smart_arrive.eraser b/skywriter-hue-integration/src/main/resources/smart_arrive.eraser new file mode 100644 index 0000000000000000000000000000000000000000..44b67cf94d3bb8da666cc38743f008552041a152 --- /dev/null +++ b/skywriter-hue-integration/src/main/resources/smart_arrive.eraser @@ -0,0 +1,44 @@ +ThingType: id="hue:bridge" label="Hue Bridge" description="The hue bridge represents the Philips hue bridge." parameters=[] channelTypes=[] ; +ThingType: id="hue:0200" label="Color Light" description="A dimmable light with changeable colors." ; +ThingType: id="hue:0210" label="Extended Color Light" description="A dimmable light with changeable colors and tunable color temperature." parameters=[] channelTypes=[]; +ThingType: id="skywriter-hat" label="SkyWriterHAT" description="SkyWriterHAT Gesture Recognition" parameters=["brokername"] channelTypes=["flick-type", "skywriter-x", "skywriter-y"] ; + +Thing: id="hue:bridge:0017880adcf4" label="Philips hue (10.8.0.160)" type="hue:bridge" channels=[] ; +Thing: id="hue:0210:0017880adcf4:1" label="Wohnzimmer" type="hue:0210" channels=["hue:0210:0017880adcf4:1:color_temperature", "hue:0210:0017880adcf4:1:effect"] ; +Thing: id="hue:0200:0017880adcf4:5" label="Hue iris 1" type="hue:0200" channels=["hue:0210:0017880adcf4:4:color", "hue:0210:0017880adcf4:4:color_temperature", "hue:0210:0017880adcf4:4:alert", "hue:0210:0017880adcf4:4:effect"] ; +Thing: id="hue:0210:0017880adcf4:4" label="Hue go 1" type="hue:0210" channels=["hue:0200:0017880adcf4:5:color", "hue:0200:0017880adcf4:5:alert", "hue:0200:0017880adcf4:5:effect"] ; +Thing: id="skywriter1" label="Our skywriter" type="skywriter-hat" channels=["skywriter1-flick", "skywriter1-x", "skywriter1-y"] ; + +ChannelType: id="hue:color" label="Color" description="The color channel allows to control the color of a light. It is also possible to dim values and switch the light on and off." itemType="Color" category="ColorLight" ; +ChannelType: id="hue:alert" label="Alert" description="The alert channel allows a temporary change to the bulb’s state." itemType="String" category="Unknown" ; +ChannelType: id="hue:effect" label="Color Loop" description="The effect channel allows putting the bulb in a color looping mode." itemType="Switch" category="ColorLight" ; +ChannelType: id="hue:color_temperature" label="Color Temperature" description="The color temperature channel allows to set the color temperature of a light from 0 (cold) to 100 (warm)." itemType="Dimmer" category="ColorLight" ; +ChannelType: id="flick-type" itemType="String" label="Last Flick" description="Last Flick detected (and its direction)" category="Motion" readOnly ; +ChannelType: id="skywriter-x" itemType="String" label="Current X coordinate" description="Current X coordinate" category="Motion" readOnly ; +ChannelType: id="skywriter-y" itemType="String" label="Current Y coordinate" description="Current Y coordinate" category="Motion" readOnly ; + +Channel: id="hue:0210:0017880adcf4:1:color_temperature" type="hue:color_temperature" links=[] ; +Channel: id="hue:0210:0017880adcf4:1:effect" type="hue:effect" links=[] ; +Channel: id="hue:0210:0017880adcf4:4:color" type="hue:color" links=[] ; +Channel: id="hue:0210:0017880adcf4:4:color_temperature" type="hue:color_temperature" links=[] ; +Channel: id="hue:0210:0017880adcf4:4:alert" type="hue:alert" links=[] ; +Channel: id="hue:0210:0017880adcf4:4:effect" type="hue:effect" links=[] ; +Channel: id="hue:0200:0017880adcf4:5:color" type="hue:color" links=["iris1_item"] ; +Channel: id="hue:0200:0017880adcf4:5:alert" type="hue:alert" links=[] ; +Channel: id="hue:0200:0017880adcf4:5:effect" type="hue:color" links=[] ; +Channel: id="skywriter1-flick" type="flick-type" links=[]; +Channel: id="skywriter1-x" type="skywriter-x" links=["skywriter1_x"]; +Channel: id="skywriter1-y" type="skywriter-y" links=["skywriter1_y"]; + +Item: id="iris1_item" label="Iris 1" state="121,88,68" topic="iris1_item/state"; +Item: id="skywriter1_x" label="X" state="0" topic="skywriter/x"; +Item: id="skywriter1_y" label="Y" state="0" topic="skywriter/y"; +//Item: id="skywriter1_xyz" label="Y" state="0" topic="skywriter/xyz"; + +Group: id="Lights" items=["iris1_item"]; +Group: id="Skywriter" items=["skywriter1_x", "skywriter1_y"]; + +Parameter: id="brokername" label="Broker Name" description="Name of the broker as defined in the <broker>.url in services/mqtt.cfg. See the MQTT Binding for more information on how to configure MQTT broker connections." type="Text" context="service" default="mosquitto" required ; + +// MqttHost is set programmatically +Mqtt: incoming="oh2/out/" outgoing="oh2/in/" ; diff --git a/stub.org.openhab.core.scriptengine.action/.gitignore b/stub.org.openhab.core.scriptengine.action/.gitignore index 83ccc54d0286590d31587a71aaa789e780bff41e..70b583e34c3316bcd77c807e2d6b85db5e7d49f6 100644 --- a/stub.org.openhab.core.scriptengine.action/.gitignore +++ b/stub.org.openhab.core.scriptengine.action/.gitignore @@ -1,2 +1,3 @@ /build/ /bin/ +logs/ diff --git a/stub.org.openhab.core.scriptengine.action/build.gradle b/stub.org.openhab.core.scriptengine.action/build.gradle index e2b54c7a70609c315c5b529678bdfb6cc2d8b461..128c3aa9c93c8dc9dd4aff8a245930bfb21b555a 100644 --- a/stub.org.openhab.core.scriptengine.action/build.gradle +++ b/stub.org.openhab.core.scriptengine.action/build.gradle @@ -1,18 +1,3 @@ -repositories { - mavenCentral() -} - -sourceCompatibility = 1.8 - -apply plugin: 'java' - -dependencies { - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' -} - sourceSets { main { java { diff --git a/stub.org.openhab.core.scriptengine.action/src/main/resources/log4j2.xml b/stub.org.openhab.core.scriptengine.action/src/main/resources/log4j2.xml index 0594576fac98ba859e411597c90c8e3d989378bd..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c 100644 --- a/stub.org.openhab.core.scriptengine.action/src/main/resources/log4j2.xml +++ b/stub.org.openhab.core.scriptengine.action/src/main/resources/log4j2.xml @@ -4,8 +4,8 @@ <Console name="Console"> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> </Console> - <RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" - filePattern="logs/jastadd-mquat-%i.log"> + <RollingFile name="RollingFile" fileName="logs/eraser.log" + filePattern="logs/eraser-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <Policies> <OnStartupTriggeringPolicy/>