From 472ca9776db7fa0be6535ff0cc59e5c9b8a7bbd4 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Thu, 2 May 2019 16:33:09 +0200
Subject: [PATCH] Working on Separation of Trees.

- Begin to integrate ML adapters, see issue #13
- Create separate tree for openhab
- Separate concrete ML models into different grammar files
- This partly makes issue #7 obsolete (ML-Model still needs to be renamed)
---
 .../src/main/jastadd/DecisionTree.jrag        | 10 +-
 .../src/main/jastadd/DecisionTree.relast      | 12 +++
 eraser-base/src/main/jastadd/Logging.jadd     |  2 +-
 .../src/main/jastadd/MachineLearning.jrag     | 62 +++++++++++--
 .../src/main/jastadd/MachineLearning.relast   | 40 +-------
 .../src/main/jastadd/ModelStatistics.jrag     |  4 +-
 eraser-base/src/main/jastadd/Navigation.jrag  | 33 ++++---
 .../src/main/jastadd/NeuralNetwork.jrag       |  2 +-
 .../src/main/jastadd/NeuralNetwork.relast     | 20 ++++
 eraser-base/src/main/jastadd/Printing.jrag    | 14 ++-
 eraser-base/src/main/jastadd/Util.jrag        |  3 +-
 eraser-base/src/main/jastadd/eraser.parser    |  8 +-
 eraser-base/src/main/jastadd/main.relast      |  3 +-
 eraser-base/src/main/jastadd/openhab.jrag     |  5 +
 eraser-base/src/main/jastadd/openhab.relast   |  3 +-
 .../java/de/tudresden/inf/st/eraser/Main.java |  8 +-
 .../deserializer/ASTNodeDeserializer.java     | 17 +++-
 .../model/InternalMachineLearningHandler.java | 54 +++++++++++
 .../model/InternalMachineLearningResult.java  | 21 +++++
 .../model}/MachineLearningDecoder.java        |  2 +-
 .../model}/MachineLearningEncoder.java        |  2 +-
 .../jastadd/model}/MachineLearningResult.java |  2 +-
 .../st/eraser/openhab2/OpenHab2Importer.java  | 28 ++----
 .../st/eraser/parser/EraserParserHelper.java  | 19 ++--
 .../inf/st/eraser/util/ParserUtils.java       |  7 +-
 .../inf/st/eraser/util/TestUtils.java         | 16 ++--
 .../inf/st/eraser/ControllingItemTest.java    |  2 +-
 .../inf/st/eraser/DecisionTreeTest.java       |  6 +-
 .../tudresden/inf/st/eraser/InfluxTest.java   | 12 ++-
 .../de/tudresden/inf/st/eraser/MqttTests.java | 55 +++++------
 .../inf/st/eraser/NeuralNetworkTest.java      |  2 +-
 .../inf/st/eraser/OpenHabImporterTest.java    |  5 +-
 .../tudresden/inf/st/eraser/ParserTests.java  | 20 ++--
 .../de/tudresden/inf/st/eraser/RulesTest.java | 92 +++++++++----------
 .../eraser/jastadd_test/core/TestRunner.java  | 17 ++--
 .../resources/openhabtest/oh1/output.eraser   |  2 -
 .../resources/openhabtest/oh2/output.eraser   |  2 -
 37 files changed, 373 insertions(+), 239 deletions(-)
 create mode 100644 eraser-base/src/main/jastadd/DecisionTree.relast
 create mode 100644 eraser-base/src/main/jastadd/NeuralNetwork.relast
 create mode 100644 eraser-base/src/main/jastadd/openhab.jrag
 create mode 100644 eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java
 create mode 100644 eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningResult.java
 rename {feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api => eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model}/MachineLearningDecoder.java (93%)
 rename {feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api => eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model}/MachineLearningEncoder.java (96%)
 rename {feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api => eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model}/MachineLearningResult.java (91%)

diff --git a/eraser-base/src/main/jastadd/DecisionTree.jrag b/eraser-base/src/main/jastadd/DecisionTree.jrag
index e8d3ae67..f50ca056 100644
--- a/eraser-base/src/main/jastadd/DecisionTree.jrag
+++ b/eraser-base/src/main/jastadd/DecisionTree.jrag
@@ -4,20 +4,20 @@ 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() = holdsFor(getItem());
diff --git a/eraser-base/src/main/jastadd/DecisionTree.relast b/eraser-base/src/main/jastadd/DecisionTree.relast
new file mode 100644
index 00000000..17a1e65c
--- /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/Logging.jadd b/eraser-base/src/main/jastadd/Logging.jadd
index 55c48fc5..dd41bc70 100644
--- a/eraser-base/src/main/jastadd/Logging.jadd
+++ b/eraser-base/src/main/jastadd/Logging.jadd
@@ -6,7 +6,7 @@ aspect Logging {
   private org.apache.logging.log4j.Logger DummyMachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(DummyMachineLearningModel.class);
   private org.apache.logging.log4j.Logger Rule.logger = org.apache.logging.log4j.LogManager.getLogger(Rule.class);
   private org.apache.logging.log4j.Logger MqttRoot.logger = org.apache.logging.log4j.LogManager.getLogger(MqttRoot.class);
-  private org.apache.logging.log4j.Logger MachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(MachineLearningModel.class);
+  private org.apache.logging.log4j.Logger InternalMachineLearningModel.logger = org.apache.logging.log4j.LogManager.getLogger(MachineLearningModel.class);
   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);
 }
diff --git a/eraser-base/src/main/jastadd/MachineLearning.jrag b/eraser-base/src/main/jastadd/MachineLearning.jrag
index 339cbda9..1a912dbd 100644
--- a/eraser-base/src/main/jastadd/MachineLearning.jrag
+++ b/eraser-base/src/main/jastadd/MachineLearning.jrag
@@ -11,7 +11,7 @@ aspect MachineLearning {
     List<ItemPreference> computePreferences();
   }
 
-  syn Leaf MachineLearningModel.classify();
+  syn Leaf InternalMachineLearningModel.classify();
 
   //--- currentActivityName ---
   syn String Root.currentActivityName() = JavaUtils.ifPresentOrElseReturn(
@@ -21,10 +21,20 @@ aspect MachineLearning {
     );
 
   //--- 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(getMachineLearningRoot().hasActivityRecognition() ?
+      extractActivityIdentifier(getMachineLearningRoot().getActivityRecognition().getDecoder().classify().getPreferences()) :
+      -1);
+  }
+  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();
+  syn List<ItemPreference> Root.currentPreferences() = getMachineLearningRoot().getPreferenceLearning().getDecoder().classify().getPreferences();
 
   //--- canSetActivity ---
   syn boolean MachineLearningModel.canSetActivity() = false;
@@ -64,7 +74,7 @@ aspect MachineLearning {
   public void DummyMachineLearningModel.connectItems(List<String> itemNames) {
     logger.info("Storing items to connect");
     for (String itemName : itemNames) {
-      JavaUtils.ifPresentOrElse(getRoot().resolveItem(itemName),
+      JavaUtils.ifPresentOrElse(getRoot().getOpenHAB2Model().resolveItem(itemName),
           this::addItem,
           () -> logger.warn("Could not resolve item '{}'", itemName));
     }
@@ -72,10 +82,10 @@ aspect MachineLearning {
 
   //--- check ---
   /**
-   * Checks the NeuralNetwork for all necessary children.
+   * Checks the ML model for all necessary children.
    * @return true, if everything is alright. false otherwise
    */
-  public boolean MachineLearningModel.check() {
+  public boolean InternalMachineLearningModel.check() {
     boolean good = true;
     if (getOutputApplication() == null) {
       logger.warn("{}: OutputApplication function is null!", mlKind());
@@ -114,4 +124,44 @@ aspect MachineLearning {
     );
   }
 
+  //... 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;
+  }
+
 }
diff --git a/eraser-base/src/main/jastadd/MachineLearning.relast b/eraser-base/src/main/jastadd/MachineLearning.relast
index 4f00aa9c..01014656 100644
--- a/eraser-base/src/main/jastadd/MachineLearning.relast
+++ b/eraser-base/src/main/jastadd/MachineLearning.relast
@@ -13,44 +13,14 @@ rel RecognitionEvent.Activity -> Activity ;
 
 ManualChangeEvent : ChangeEvent ;
 
-abstract MachineLearningModel ::= <OutputApplication:DoubleDoubleFunction> ;
+abstract MachineLearningModel ::= ;
+ExternalMachineLearningModel : MachineLearningModel ;
+abstract InternalMachineLearningModel : MachineLearningModel ::= <OutputApplication:DoubleDoubleFunction> ;
+rel InternalMachineLearningModel.RelevantItem* -> Item ;
+rel InternalMachineLearningModel.TargetItem* -> Item ;
 
 abstract ItemPreference ::= ;
 rel ItemPreference.Item -> Item ;
 
 ItemPreferenceColor : ItemPreference ::= <PreferredHSB:TupleHSB> ;
 ItemPreferenceDouble : ItemPreference ::= <PreferredValue:double> ;
-
-// ----------------    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> ;
-rel ItemStateCheck.Item -> Item ;
-
-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> ;
-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 : MachineLearningModel ::= Current:DecisionTreeLeaf ;
-rel DummyMachineLearningModel.Item* -> Item ;
diff --git a/eraser-base/src/main/jastadd/ModelStatistics.jrag b/eraser-base/src/main/jastadd/ModelStatistics.jrag
index 293fcd92..443635f1 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 9aadd37e..69592c1b 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);
@@ -136,15 +139,11 @@ aspect Navigation {
 
   //--- 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.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 d2953b92..e673b69a 100644
--- a/eraser-base/src/main/jastadd/NeuralNetwork.jrag
+++ b/eraser-base/src/main/jastadd/NeuralNetwork.jrag
@@ -100,7 +100,7 @@ aspect NeuralNetwork {
       }
       String itemName = itemNames.get(i);
       InputNeuron neuron = getInputNeuron(i);
-      JavaUtils.ifPresentOrElse(getRoot().resolveItem(itemName),
+      JavaUtils.ifPresentOrElse(getRoot().getOpenHAB2Model().resolveItem(itemName),
           neuron::setItem,
           () -> logger.warn("Could not resolve item '{}'", itemName));
     }
diff --git a/eraser-base/src/main/jastadd/NeuralNetwork.relast b/eraser-base/src/main/jastadd/NeuralNetwork.relast
new file mode 100644
index 00000000..1214eb19
--- /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 3bfa5280..3007db52 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,9 +36,6 @@ aspect Printing {
     for (Channel c : channels()) {
       sb.append(c.prettyPrint());
     }
-    sb.append(getMqttRoot().prettyPrint());
-    sb.append(getInfluxRoot().prettyPrint());
-    sb.append(getMachineLearningRoot().prettyPrint());
     return sb.toString();
   }
 
diff --git a/eraser-base/src/main/jastadd/Util.jrag b/eraser-base/src/main/jastadd/Util.jrag
index 3876239d..282a4441 100644
--- a/eraser-base/src/main/jastadd/Util.jrag
+++ b/eraser-base/src/main/jastadd/Util.jrag
@@ -27,8 +27,9 @@ aspect Util {
 
   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.parser b/eraser-base/src/main/jastadd/eraser.parser
index dbd6457e..acd54d7c 100644
--- a/eraser-base/src/main/jastadd/eraser.parser
+++ b/eraser-base/src/main/jastadd/eraser.parser
@@ -25,12 +25,12 @@ import java.util.HashMap;
 %goal goal;
 
 Root goal =
-     thing.t goal.r                     {: insertZero(r.getThingList(), t); return r; :}
+     thing.t goal.r                     {: insertZero(r.getOpenHAB2Model().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; :}
+  |  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.getChannelTypeList(), ct); 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; :}
diff --git a/eraser-base/src/main/jastadd/main.relast b/eraser-base/src/main/jastadd/main.relast
index 45dd621a..066ffbbc 100644
--- a/eraser-base/src/main/jastadd/main.relast
+++ b/eraser-base/src/main/jastadd/main.relast
@@ -1,6 +1,5 @@
 // ----------------    Main    ------------------------------
-Root ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* User* MqttRoot InfluxRoot
-         MachineLearningRoot Rule* Location* ;
+Root ::= OpenHAB2Model User* MqttRoot InfluxRoot MachineLearningRoot Rule* Location* ;
 
 // ----------------    Users   ------------------------------
 User : LabelledModelElement ;
diff --git a/eraser-base/src/main/jastadd/openhab.jrag b/eraser-base/src/main/jastadd/openhab.jrag
new file mode 100644
index 00000000..6d30e4b3
--- /dev/null
+++ b/eraser-base/src/main/jastadd/openhab.jrag
@@ -0,0 +1,5 @@
+aspect OpenHAB2 {
+  syn ActivityItem OpenHAB2Model.getActivityItem() {
+    return new ActivityItem();
+  }
+}
diff --git a/eraser-base/src/main/jastadd/openhab.relast b/eraser-base/src/main/jastadd/openhab.relast
index 6f5b378e..a5cde155 100644
--- a/eraser-base/src/main/jastadd/openhab.relast
+++ b/eraser-base/src/main/jastadd/openhab.relast
@@ -1,4 +1,6 @@
 // ----------------    openHAB    ------------------------------
+OpenHAB2Model ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* /ActivityItem:Item/ ;
+
 abstract ModelElement ::= <ID:String> ;
 abstract LabelledModelElement : ModelElement ::= <Label:String> ;
 abstract DescribableModelElement : LabelledModelElement ::= <Description:String> ;
@@ -53,4 +55,3 @@ 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 9b98f688..40b84e5a 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
@@ -16,7 +16,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 {
@@ -70,7 +70,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 +79,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 +106,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 95f1d470..d349331d 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/InternalMachineLearningHandler.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java
new file mode 100644
index 00000000..2c59f46c
--- /dev/null
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/InternalMachineLearningHandler.java
@@ -0,0 +1,54 @@
+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 newData(Root model, 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 00000000..087020b3
--- /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/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningDecoder.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java
similarity index 93%
rename from feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningDecoder.java
rename to eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java
index 2c29aa28..a6e01fb9 100644
--- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningDecoder.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningDecoder.java
@@ -1,4 +1,4 @@
-package de.tudresden.inf.st.eraser.feedbackloop.api;
+package de.tudresden.inf.st.eraser.jastadd.model;
 
 import java.time.Instant;
 
diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningEncoder.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java
similarity index 96%
rename from feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningEncoder.java
rename to eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java
index dff85f27..8cf4d78f 100644
--- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningEncoder.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningEncoder.java
@@ -1,4 +1,4 @@
-package de.tudresden.inf.st.eraser.feedbackloop.api;
+package de.tudresden.inf.st.eraser.jastadd.model;
 
 import de.tudresden.inf.st.eraser.jastadd.model.Item;
 import de.tudresden.inf.st.eraser.jastadd.model.Root;
diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningResult.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java
similarity index 91%
rename from feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningResult.java
rename to eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java
index d57ccc81..eebd16d3 100644
--- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/MachineLearningResult.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/jastadd/model/MachineLearningResult.java
@@ -1,4 +1,4 @@
-package de.tudresden.inf.st.eraser.feedbackloop.api;
+package de.tudresden.inf.st.eraser.jastadd.model;
 
 import de.tudresden.inf.st.eraser.jastadd.model.ItemPreference;
 
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 19f0dff8..4e1742c7 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
@@ -42,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
@@ -58,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);
@@ -84,17 +85,6 @@ public class OpenHab2Importer {
         ThingTypeData data = mapper.readValue(makeURL(thingTypeDetailUrl, hostAndPort, thingType.getID()), ThingTypeData.class);
         update(thingType, data);
       }
-
-      // 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);
-
       return model;
     } catch (IOException e) {
       logger.catching(e);
@@ -110,7 +100,7 @@ public class OpenHab2Importer {
     return URI.create(String.format(formatUrlString, hostAndPort, id)).toURL();
   }
 
-  private void update(Root model, ThingTypeData[] thingTypeList) {
+  private void update(OpenHAB2Model model, ThingTypeData[] thingTypeList) {
     for (ThingTypeData thingTypeData : thingTypeList) {
       ThingType thingType = new ThingType();
       thingType.setID(thingTypeData.UID);
@@ -120,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);
@@ -173,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);
@@ -192,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<>();
@@ -309,7 +299,7 @@ public class OpenHab2Importer {
     }
   }
 
-  private void update(Root model, LinkData[] linkList) {
+  private void update(OpenHAB2Model model, LinkData[] linkList) {
     for (LinkData linkData : linkList) {
       ifPresent(model.resolveChannel(linkData.channelUID), "Channel", linkData,
           channel -> ifPresent(model.resolveItem(linkData.itemName), "Item", linkData, channel::addLinkedItem));
@@ -351,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/parser/EraserParserHelper.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java
index ea392f2a..8c92ddc1 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
@@ -115,13 +115,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();
   }
 
@@ -129,7 +129,7 @@ public class EraserParserHelper {
     Map<String, ItemCategory> newCategories = new HashMap<>();
     missingItemCategoryMap.forEach((item, category) ->
         item.setCategory(newCategories.computeIfAbsent(category, ItemCategory::new)));
-    newCategories.values().forEach(root::addItemCategory);
+    newCategories.values().forEach(node -> root.getOpenHAB2Model().addItemCategory(node));
   }
 
   private void checkUnusedElements() {
@@ -417,34 +417,31 @@ public class EraserParserHelper {
   //--- 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;
   }
 
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 61504717..40da66ff 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,10 +3,7 @@ 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 org.apache.logging.log4j.LogManager;
@@ -172,7 +169,7 @@ public class ParserUtils {
    * @param model         The model to operate on
    * @param danglingItems A list of items to add to the new group
    */
-  public static void createUnknownGroup(Root model, Collection<Item> danglingItems) {
+  public static void createUnknownGroup(OpenHAB2Model model, Collection<Item> danglingItems) {
     Group unknownGroup = new Group();
     unknownGroup.setID("Unknown");
     model.addGroup(unknownGroup);
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
index f31ffd14..5f07ec7c 100644
--- 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
@@ -10,9 +10,9 @@ import de.tudresden.inf.st.eraser.jastadd.model.*;
 public class TestUtils {
 
   public static class ModelAndItem {
-    public Root model;
+    public OpenHAB2Model model;
     public NumberItem item;
-    static ModelAndItem of(Root model, NumberItem item) {
+    static ModelAndItem of(OpenHAB2Model model, NumberItem item) {
       ModelAndItem result = new ModelAndItem();
       result.model = model;
       result.item = item;
@@ -25,21 +25,21 @@ public class TestUtils {
   }
 
   public static ModelAndItem createModelAndItem(double initialValue, boolean useUpdatingItem) {
-    Root model = Root.createEmptyRoot();
+    Root root = Root.createEmptyRoot();
     Group g = new Group();
-    model.addGroup(g);
+    root.getOpenHAB2Model().addGroup(g);
     g.setID("group1");
 
     NumberItem item = addItemTo(g, initialValue, useUpdatingItem);
 
-    return ModelAndItem.of(model, item);
+    return ModelAndItem.of(root.getOpenHAB2Model(), item);
   }
 
-  public static NumberItem addItemTo(Root model, double initialValue) {
+  public static NumberItem addItemTo(OpenHAB2Model model, double initialValue) {
     return addItemTo(model, initialValue, false);
   }
 
-  public static NumberItem addItemTo(Root model, double initialValue, boolean useUpdatingItem) {
+  public static NumberItem addItemTo(OpenHAB2Model model, double initialValue, boolean useUpdatingItem) {
     return addItemTo(getDefaultGroup(model), initialValue, useUpdatingItem);
   }
 
@@ -56,7 +56,7 @@ public class TestUtils {
     return item;
   }
 
-  public static Group getDefaultGroup(Root model) {
+  public static Group getDefaultGroup(OpenHAB2Model model) {
     // use first found group
     return model.getGroup(0);
   }
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
index 612bda86..edc98e72 100644
--- 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
@@ -58,7 +58,7 @@ public class ControllingItemTest {
   @Test
   public void testManyToOneColor() {
     ModelAndItem mai = TestUtils.createModelAndItem(0);
-    Root model = mai.model;
+    OpenHAB2Model model = mai.model;
     NumberItem numberItem = mai.item;
 
     Group g = TestUtils.getDefaultGroup(model);
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 89fbacb6..fa93e069 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
@@ -17,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);
@@ -44,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");
@@ -130,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/InfluxTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/InfluxTest.java
index 5fd3a3ac..4f8005be 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
@@ -95,7 +95,7 @@ public class InfluxTest {
 
   @Test
   public void justAdapter() {
-    InfluxAdapter influxAdapter = mai.model.getInfluxRoot().influxAdapter();
+    InfluxAdapter influxAdapter = getInfluxRoot().influxAdapter();
     Assume.assumeTrue("Adapter not connected", influxAdapter.isConnected());
     influxAdapter.deleteDatabase();
 
@@ -151,12 +151,16 @@ public class InfluxTest {
       influxRoot.setDbName(InfluxTest.class.getSimpleName());
       influxRoot.setHostByName("vm2098.zih.tu-dresden.de");
     }
-    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;
@@ -172,13 +176,13 @@ public class InfluxTest {
   @Before
   public void setNewModel() {
     mai = createModel();
-    mai.model.getInfluxRoot().influxAdapter().disableAsyncQuery();
+    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/MqttTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MqttTests.java
index 237a71b4..331a9d8d 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
@@ -48,21 +48,21 @@ public class MqttTests {
 
   @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());
@@ -76,11 +76,11 @@ public class MqttTests {
   public void itemUpdateSend1() throws Exception {
     String expectedTopic = outgoingPrefix + "/" + firstPart + "/" + secondPart;
 
-    RootItemAndTwoTopics rootAB = createAB();
-    assertSenderConnected(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);
 
@@ -102,20 +102,20 @@ public class MqttTests {
     String expectedTopic1 = outgoingPrefix + "/" + firstPart + "/" + secondPart;
     String expectedTopic2 = outgoingPrefix + "/" + alternativeFirstPart + "/" + secondPart;
 
-    RootItemAndTwoTopics rootAB = createAB();
-    assertSenderConnected(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);
+    modelAB.model.getRoot().getMqttRoot().addTopic(alternative);
 
-    NumberItem item2 = TestUtils.addItemTo(rootAB.model, 0);
+    NumberItem item2 = TestUtils.addItemTo(modelAB.model, 0);
     item2.setTopic(alternativeB);
 
     createMqttReceiver(expectedTopic1, expectedTopic2);
@@ -141,16 +141,17 @@ public class MqttTests {
     Assert.assertThat(messages, hasItem(Double.toString(thirdState)));
   }
 
-  private void assertSenderConnected(Root model) {
+  private void assertSenderConnected(ModelItemAndTwoTopics modelAB) {
+    MqttRoot mqttRoot = modelAB.model.getRoot().getMqttRoot();
     mqttBroker.waitingFor(Wait.forHealthcheck());
-    if (!model.getMqttRoot().getMqttSender().isConnected()) {
+    if (!mqttRoot.getMqttSender().isConnected()) {
       try {
         Thread.sleep(1000);
       } catch (InterruptedException e) {
         logger.catching(e);
       }
     }
-    Assert.assertTrue("Broker is not connected", model.getMqttRoot().getMqttSender().isConnected());
+    Assert.assertTrue("Broker is not connected", mqttRoot.getMqttSender().isConnected());
   }
 
   private void createMqttReceiver(String... expectedTopics) throws IOException {
@@ -171,9 +172,9 @@ public class MqttTests {
     receiver.waitUntilReady(2, TimeUnit.SECONDS);
   }
 
-  private RootItemAndTwoTopics createAB() {
+  private ModelItemAndTwoTopics createAB() {
     TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0);
-    Root model = mai.model;
+    OpenHAB2Model model = mai.model;
     MqttRoot mqttRoot = new MqttRoot();
     mqttRoot.setIncomingPrefix("inc");
     mqttRoot.setOutgoingPrefix(outgoingPrefix);
@@ -192,17 +193,17 @@ public class MqttTests {
     a.addSubTopic(ab);
     mqttRoot.addTopic(a);
     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;
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 5d658500..0eb593fd 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
@@ -24,7 +24,7 @@ public class NeuralNetworkTest {
      */
 
     NeuralNetworkRoot neuralNetworkRoot = NeuralNetworkRoot.createEmpty();
-    mai.model.getMachineLearningRoot().setPreferenceLearning(neuralNetworkRoot);
+    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 37766dac..8c109638 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;
@@ -70,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;
@@ -88,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
index 02a6d30a..02f2da81 100644
--- 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
@@ -20,8 +20,8 @@ public class ParserTests {
   public void testCreateMqttTopicSimple() {
     ModelAndItem mai = TestUtils.createModelAndItem(1);
 
-    Root model = mai.model;
-    model.setMqttRoot(new MqttRoot());
+    Root root = mai.model.getRoot();
+    root.setMqttRoot(new MqttRoot());
     NumberItem item = mai.item;
 
     Assert.assertNull(item.getTopic());
@@ -29,11 +29,11 @@ public class ParserTests {
     String[] parts = { "one", "two", "three" };
     String topicName = String.join("/", parts);
 
-    ParserUtils.createMqttTopic(item, topicName, model);
+    ParserUtils.createMqttTopic(item, topicName, root);
 
     Assert.assertNotNull(item.getTopic());
 
-    MqttRoot mqttRoot = model.getMqttRoot();
+    MqttRoot mqttRoot = root.getMqttRoot();
     Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic());
     Assert.assertEquals("First part is wrong",
         parts[0], mqttRoot.getTopic(0).getPart());
@@ -56,10 +56,10 @@ public class ParserTests {
   public void testCreateMqttTopicTwoInterleavedTopics() {
     ModelAndItem mai = TestUtils.createModelAndItem(1);
 
-    Root model = mai.model;
-    model.setMqttRoot(new MqttRoot());
+    Root root = mai.model.getRoot();
+    root.setMqttRoot(new MqttRoot());
     NumberItem item1 = mai.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 3);
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 3);
 
     Assert.assertNull(item1.getTopic());
     Assert.assertNull(item2.getTopic());
@@ -69,13 +69,13 @@ public class ParserTests {
     String topicName1 = String.join("/", parts);
     String topicName2 = String.join("/", parts[0], otherPart2, parts[2]);
 
-    ParserUtils.createMqttTopic(item1, topicName1, model);
-    ParserUtils.createMqttTopic(item2, topicName2, model);
+    ParserUtils.createMqttTopic(item1, topicName1, root);
+    ParserUtils.createMqttTopic(item2, topicName2, root);
 
     Assert.assertNotNull(item1.getTopic());
     Assert.assertNotNull(item2.getTopic());
 
-    MqttRoot mqttRoot = model.getMqttRoot();
+    MqttRoot mqttRoot = root.getMqttRoot();
     Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic());
     Assert.assertEquals("First part is wrong",
         parts[0], mqttRoot.getTopic(0).getPart());
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 1ec9aaef..2c318278 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
@@ -50,13 +50,13 @@ public class RulesTest {
   @Test
   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();
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -74,11 +74,11 @@ public class RulesTest {
   @Test
   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();
-    model.addRule(rule);
+    root.addRule(rule);
 
     rule.activateFor(item);
     rule.activateFor(item);
@@ -94,18 +94,18 @@ public class RulesTest {
   @Test
   public void testTwoRulesForOneItem() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(4);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
 
     Counters counter1 = new Counters();
     Rule ruleA = new Rule();
     ruleA.addAction(new LambdaAction(counter1));
-    model.addRule(ruleA);
+    root.addRule(ruleA);
 
     Rule ruleB = new Rule();
     Counters counter2 = new Counters();
     ruleB.addAction(new LambdaAction(counter2));
-    model.addRule(ruleB);
+    root.addRule(ruleB);
 
     ruleA.activateFor(item);
     ruleB.activateFor(item);
@@ -129,14 +129,14 @@ public class RulesTest {
   @Test
   public void testOneRuleForTwoItems() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(4);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item1 = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem);
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
 
     Rule rule = new Rule();
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
 
     rule.activateFor(item1);
     rule.activateFor(item2);
@@ -188,13 +188,13 @@ public class RulesTest {
   @Test
   public void testRemoveActivation() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(4);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
 
     Rule rule = new Rule();
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
 
     rule.activateFor(item);
 
@@ -212,7 +212,7 @@ public class RulesTest {
   @Test
   public void testNumberConditions() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(2);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
 
     Rule rule = new Rule();
@@ -222,7 +222,7 @@ public class RulesTest {
     rule.addCondition(new ItemStateCheckCondition(check2));
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -246,7 +246,7 @@ public class RulesTest {
   @Test
   public void testTwoActions() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(2);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
 
     Rule rule = new Rule();
@@ -254,7 +254,7 @@ public class RulesTest {
     rule.addAction(new LambdaAction(counter1));
     Counters counter2 = new Counters();
     rule.addAction(new LambdaAction(counter2));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("First counter not initialized correctly"), 0, counter1.get(item));
@@ -272,9 +272,9 @@ public class RulesTest {
   @Test
   public void testChainedRules() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(2);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem);
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
 
     Rule ruleA = new Rule();
     Counters counter1 = new Counters();
@@ -286,8 +286,8 @@ public class RulesTest {
 
     ruleA.addAction(new TriggerRuleAction(ruleB));
 
-    model.addRule(ruleA);
-    model.addRule(ruleB);
+    root.addRule(ruleA);
+    root.addRule(ruleB);
     ruleA.activateFor(item);
     ruleB.activateFor(item2);
 
@@ -315,15 +315,15 @@ public class RulesTest {
   @Test
   public void testSetStateFromConstantStringAction() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem);
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
 
     Rule rule = new Rule();
     rule.addAction(new SetStateFromConstantStringAction(item2, "5"));
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Affected item not initialized correctly"),
@@ -347,15 +347,15 @@ public class RulesTest {
   public void testSetStateFromLambdaAction() {
     ValuedStateProvider provider = new ValuedStateProvider();
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(0);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 3, useUpdatingItem);
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 3, useUpdatingItem);
 
     Rule rule = new Rule();
     rule.addAction(new SetStateFromLambdaAction(item2, provider));
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Affected item not initialized correctly"),
@@ -389,15 +389,15 @@ public class RulesTest {
   @Test
   public void testSetStateFromTriggeringItemAction() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    StringItem item2 = addStringItem(model, "0");
+    StringItem item2 = addStringItem(root.getOpenHAB2Model(), "0");
 
     Rule rule = new Rule();
     Counters counter = new Counters();
     rule.addAction(new SetStateFromTriggeringItemAction(item2));
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -419,10 +419,10 @@ public class RulesTest {
   @Test
   public void testSetStateFromItemsAction() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem);
-    StringItem affectedItem = addStringItem(model, "1");
+    NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
+    StringItem affectedItem = addStringItem(root.getOpenHAB2Model(), "1");
 
     Rule rule = new Rule();
     SetStateFromItemsAction action = new SetStateFromItemsAction(items ->
@@ -435,7 +435,7 @@ public class RulesTest {
     rule.addAction(action);
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -467,7 +467,7 @@ public class RulesTest {
         "12", affectedItem.getState());
 
     // add new item to sum
-    NumberItem item3 = TestUtils.addItemTo(model, -4, useUpdatingItem);
+    NumberItem item3 = TestUtils.addItemTo(root.getOpenHAB2Model(), -4, useUpdatingItem);
     action.addSourceItem(item3);
 
     // still 7 + 5 = 12, as rule should not trigger
@@ -486,15 +486,15 @@ public class RulesTest {
   @Test
   public void testAddDoubleToStateAction() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem affectedItem = TestUtils.addItemTo(model, 4, useUpdatingItem);
+    NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
 
     Rule rule = new Rule();
     rule.addAction(new AddDoubleToStateAction(affectedItem, 2));
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -523,15 +523,15 @@ public class RulesTest {
   @Test
   public void testMultiplyDoubleToStateAction() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item = modelAndItem.item;
-    NumberItem affectedItem = TestUtils.addItemTo(model, 4, useUpdatingItem);
+    NumberItem affectedItem = TestUtils.addItemTo(root.getOpenHAB2Model(), 4, useUpdatingItem);
 
     Rule rule = new Rule();
     rule.addAction(new MultiplyDoubleToStateAction(affectedItem, 2));
     Counters counter = new Counters();
     rule.addAction(new LambdaAction(counter));
-    model.addRule(rule);
+    root.addRule(rule);
     rule.activateFor(item);
 
     Assert.assertEquals(m("Counter not initialized correctly"), 0, counter.get(item));
@@ -560,10 +560,10 @@ public class RulesTest {
   @Test
   public void testChainAddMultiplyActions() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(3);
-    Root model = modelAndItem.model;
+    Root root = modelAndItem.model.getRoot();
     NumberItem item1 = modelAndItem.item;
-    NumberItem item2 = TestUtils.addItemTo(model, 4, useUpdatingItem);
-    NumberItem affectedItem = TestUtils.addItemTo(model, 5, useUpdatingItem);
+    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));
@@ -577,8 +577,8 @@ public class RulesTest {
 
     ruleA.addAction(new TriggerRuleAction(ruleB));
 
-    model.addRule(ruleA);
-    model.addRule(ruleB);
+    root.addRule(ruleA);
+    root.addRule(ruleB);
     ruleA.activateFor(item1);
     ruleB.activateFor(item2);
 
@@ -653,7 +653,7 @@ public class RulesTest {
     return message + " (Using " + name + ")";
   }
 
-  private StringItem addStringItem(Root model, String initialValue) {
+  private StringItem addStringItem(OpenHAB2Model model, String initialValue) {
     StringItem item = new StringItem();
     Group group = TestUtils.getDefaultGroup(model);
     item.setID("item" + group.getNumItem());
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 984fb21d..6978df5c 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/openhabtest/oh1/output.eraser b/eraser-base/src/test/resources/openhabtest/oh1/output.eraser
index c4228400..1844189c 100644
--- a/eraser-base/src/test/resources/openhabtest/oh1/output.eraser
+++ b/eraser-base/src/test/resources/openhabtest/oh1/output.eraser
@@ -18,5 +18,3 @@ Color Item: id="wohnzimmer_item" label="Wohnzimmer" state="0,0,0" category="Ligh
 Color Item: id="iris1_item" label="Iris 1" state="226,100,98" category="Lighting" ;
 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: host="localhost" ;
-Influx: host="localhost" ;
diff --git a/eraser-base/src/test/resources/openhabtest/oh2/output.eraser b/eraser-base/src/test/resources/openhabtest/oh2/output.eraser
index 45f5a639..e6354c12 100644
--- a/eraser-base/src/test/resources/openhabtest/oh2/output.eraser
+++ b/eraser-base/src/test/resources/openhabtest/oh2/output.eraser
@@ -85,5 +85,3 @@ Channel: id="openlicht:samsung-s6:2ca84896:rotation-x" type="openlicht:rotation-
 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: host="localhost" ;
-Influx: host="localhost" ;
-- 
GitLab