diff --git a/eraser-base/src/main/jastadd/AdditionalTypes.jadd b/eraser-base/src/main/jastadd/AdditionalTypes.jadd
index 32651117408970af5b3a57f4ddc48e2f7f2a0d8c..8abddb34f36d87d7f889dfd245fe83b017ae4069 100644
--- a/eraser-base/src/main/jastadd/AdditionalTypes.jadd
+++ b/eraser-base/src/main/jastadd/AdditionalTypes.jadd
@@ -1,16 +1,19 @@
 aspect AdditionalTypes {
-  public class StringList extends beaver.Symbol implements Iterable<String> {
-    private java.util.Deque<String> delegatee = new java.util.ArrayDeque<>();
+  public class ReversedList<T> extends beaver.Symbol implements Iterable<T> {
+    private java.util.Deque<T> delegatee = new java.util.ArrayDeque<>();
 
-    public java.util.Iterator<String> iterator() {
+    public java.util.Iterator<T> iterator() {
       return delegatee.descendingIterator();
     }
 
-    public void add(String s) {
-      delegatee.add(s);
+    public void add(T t) {
+      delegatee.add(t);
     }
   }
 
+  public class StringList extends ReversedList<String> {}
+  public class ItemList extends ReversedList<Item> {}
+
   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<>();
 
diff --git a/eraser-base/src/main/jastadd/Item.jrag b/eraser-base/src/main/jastadd/Item.jrag
index 5c03824f96b9de7db5a5ddeb0c4a330b516db4f9..ece1fd875c26b2d6f4bc94aa712f9bfe56a8884b 100644
--- a/eraser-base/src/main/jastadd/Item.jrag
+++ b/eraser-base/src/main/jastadd/Item.jrag
@@ -281,6 +281,8 @@ aspect ItemHandling {
     stateUpdated(shouldSendState);
   }
 
+  syn ItemObserver Item.getItemObserver() = new ItemObserver();
+
   //--- stateUpdated ---
   /**
    * Called, whenever the state of an item is updated. Does various things including:
@@ -300,9 +302,7 @@ aspect ItemHandling {
         logger.catching(e);
       }
     }
-    if (hasItemObserver()) {
-      getItemObserver().apply();
-    }
+    getItemObserver().apply();
   }
 
   //--- sendState ---
@@ -407,6 +407,7 @@ aspect ItemHandling {
         SetStateFromTriggeringItemAction action = new SetStateFromTriggeringItemAction();
         action.setAffectedItem(controlledItem);
         rule.addAction(action);
+
         rule.activateFor(controllerItem);
 
         return rule;
@@ -464,6 +465,7 @@ aspect ItemHandling {
 
   //--- copyStateTo ---
   protected abstract void Item.copyStateTo(Item stateReceiver);
+
   protected void ItemWithBooleanState.copyStateTo(Item stateReceiver) {
     stateReceiver.setStateFromBoolean(this.getState());
   }
@@ -480,6 +482,9 @@ aspect ItemHandling {
     stateReceiver.setStateFromInstant(this.getState());
   }
 
+
+
+
   private void ColorItem.setBrightness(int value) {
     setState(getState().withDifferentBrightness(value));
   }
diff --git a/eraser-base/src/main/jastadd/Navigation.jrag b/eraser-base/src/main/jastadd/Navigation.jrag
index 3e34287f52b221ad4525c3c0766db2f8b8ae697a..dd4e283a155b68dd432303debd93dd094aee06eb 100644
--- a/eraser-base/src/main/jastadd/Navigation.jrag
+++ b/eraser-base/src/main/jastadd/Navigation.jrag
@@ -29,104 +29,6 @@ aspect Navigation {
     return result;
   }
 
-  //--- resolveThingType ---
-  syn java.util.Optional<ThingType> SmartHomeEntityModel.resolveThingType(String thingTypeId) {
-    for (ThingType thingType : this.getThingTypeList()) {
-      if (thingType.getID().equals(thingTypeId)) {
-        return java.util.Optional.of(thingType);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveChannel ---
-  syn java.util.Optional<Channel> SmartHomeEntityModel.resolveChannel(String channelId) {
-    for (Thing thing : this.getThingList()) {
-      for (Channel channel : thing.getChannelList()) {
-        if (channel.getID().equals(channelId)) {
-          return java.util.Optional.of(channel);
-        }
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveChannelType ---
-  syn java.util.Optional<ChannelType> SmartHomeEntityModel.resolveChannelType(String channelTypeId) {
-    for (ChannelType channelType : this.getChannelTypeList()) {
-      if (channelType.getID().equals(channelTypeId)) {
-        return java.util.Optional.of(channelType);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveItem ---
-  syn java.util.Optional<Item> SmartHomeEntityModel.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);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveGroup ---
-  syn java.util.Optional<Group> SmartHomeEntityModel.resolveGroup(String groupId) {
-    for (Group group : this.getGroupList()) {
-      if (group.getID().equals(groupId)) {
-        return java.util.Optional.of(group);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveMqttTopic ---
-  syn java.util.Optional<MqttTopic> Root.resolveMqttTopic(String mqttTopicId) {
-    return this.getMqttRoot().resolveTopic(mqttTopicId);
-  }
-
-  //--- resolveItemCategory ---
-  syn java.util.Optional<ItemCategory> SmartHomeEntityModel.resolveItemCategory(String categoryName) {
-    for (ItemCategory category : getItemCategoryList()) {
-      if (category.getName().equals(categoryName)) {
-        return java.util.Optional.of(category);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveActivity ---
-  syn java.util.Optional<Activity> Root.resolveActivity(int identifier) {
-    for (Activity activity : getMachineLearningRoot().getActivityList()) {
-      if (activity.getIdentifier() == identifier) {
-        return java.util.Optional.of(activity);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-  syn java.util.Optional<Activity> Root.resolveActivity(String label) {
-    for (Activity activity : getMachineLearningRoot().getActivityList()) {
-      if (activity.getLabel().equals(label)) {
-        return java.util.Optional.of(activity);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
-  //--- resolveChangeEvent ---
-  syn java.util.Optional<ChangeEvent> Root.resolveChangeEvent(int identifier) {
-    for (ChangeEvent changeEvent : getMachineLearningRoot().getChangeEventList()) {
-      if (changeEvent.getIdentifier() == identifier) {
-        return java.util.Optional.of(changeEvent);
-      }
-    }
-    return java.util.Optional.empty();
-  }
-
   //--- containingThing ---
   inh Thing Channel.containingThing();
   eq Thing.getChannel().containingThing() = this;
diff --git a/eraser-base/src/main/jastadd/Printing.jrag b/eraser-base/src/main/jastadd/Printing.jrag
index 0b454039a9b84c02c68f2cef4593c0a334b99b37..80661e43e7f221d38f8c5e242abe0bd3753259ec 100644
--- a/eraser-base/src/main/jastadd/Printing.jrag
+++ b/eraser-base/src/main/jastadd/Printing.jrag
@@ -3,7 +3,7 @@ aspect Printing {
 
   String ASTNode.safeID(ModelElement elem) { return elem == null ? "NULL" : elem.getID(); }
 
-  syn String Root.prettyPrint() {
+  eq Root.prettyPrint() {
     StringBuilder sb = new StringBuilder();
     sb.append(getSmartHomeEntityModel().prettyPrint());
     sb.append(getMqttRoot().prettyPrint());
@@ -13,7 +13,7 @@ aspect Printing {
   }
 
 //--- SmartHomeEntityModel.prettyPrint() ---
-  syn String SmartHomeEntityModel.prettyPrint() {
+  eq SmartHomeEntityModel.prettyPrint() {
     StringBuilder sb = new StringBuilder();
     for (Thing t : getThingList()) {
       sb.append(t.prettyPrint());
@@ -40,7 +40,7 @@ aspect Printing {
   }
 
 //Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ;
-  syn String Thing.prettyPrint() {
+  eq Thing.prettyPrint() {
     return new MemberPrinter("Thing")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -50,7 +50,7 @@ aspect Printing {
   }
 
 //ITEM_TYPE Item: id="" label="" state="" category="" topic="";
-  syn String Item.prettyPrint() {
+  eq Item.prettyPrint() {
     return new MemberPrinter(prettyPrintType())
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -79,7 +79,7 @@ aspect Printing {
   eq DefaultItem.prettyPrintType() = "Item" ;
 
   // special ActivityItem printing. Always omit state.
-  syn String ActivityItem.prettyPrint() {
+  eq ActivityItem.prettyPrint() {
     return new MemberPrinter(prettyPrintType())
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -94,7 +94,7 @@ aspect Printing {
 
 //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() {
+  eq Group.prettyPrint() {
     return new MemberPrinter("Group")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -104,7 +104,6 @@ aspect Printing {
         .build();
   }
 
-  syn String GroupAggregationFunction.prettyPrint();
   eq SimpleGroupAggregationFunction.prettyPrint() {
     if (getFunctionName() == SimpleGroupAggregationFunctionName.EQUALITY) {
       return "";
@@ -120,7 +119,7 @@ aspect Printing {
   }
 
 //ThingType: id="" label="" description="" parameters=["PARAM_ID", "PARAM_ID"] channelTypes=["CHANNEL_TYPE_ID", "CHANNEL_TYPE_ID"];
-  syn String ThingType.prettyPrint() {
+  eq ThingType.prettyPrint() {
     return new MemberPrinter("ThingType")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -131,7 +130,7 @@ aspect Printing {
   }
 
 //Parameter: id="" label="" description="" type="" default="" required;
-  syn String Parameter.prettyPrint() {
+  eq Parameter.prettyPrint() {
     return new MemberPrinter("Parameter")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -144,7 +143,7 @@ aspect Printing {
   }
 
 //ChannelType: id="" label="" description="" itemType="" category="" readyOnly;
-  syn String ChannelType.prettyPrint() {
+  eq ChannelType.prettyPrint() {
     return new MemberPrinter("ChannelType")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
@@ -160,7 +159,7 @@ aspect Printing {
   syn String SimpleChannelCategory.prettyPrint() = getValue();
 
 //Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"];
-  syn String Channel.prettyPrint() {
+  eq Channel.prettyPrint() {
     return new MemberPrinter("Channel")
         .addRequired("id", getID())
         .addRequired("type", getType(), ChannelType::getID)
@@ -178,7 +177,7 @@ aspect Printing {
   }
 
 //Mqtt: incoming="" outgoing="" host="";
-  syn String MqttRoot.prettyPrint() {
+  eq MqttRoot.prettyPrint() {
     return new MemberPrinter("Mqtt")
         .addNonDefault("incoming", getIncomingPrefix())
         .addNonDefault("outgoing", getOutgoingPrefix())
@@ -187,7 +186,7 @@ aspect Printing {
   }
 
 //Influx: user="" password="" dbName="" host="" ;
-  syn String InfluxRoot.prettyPrint() {
+  eq InfluxRoot.prettyPrint() {
     return new MemberPrinter("Influx")
         .addNonDefault("user", getUser(), DEFAULT_USER)
         .addNonDefault("password", getPassword(), DEFAULT_PASSWORD)
@@ -197,7 +196,7 @@ aspect Printing {
   }
 
 // Activities: { index: "name" }
-  syn String MachineLearningRoot.prettyPrint() {
+  eq MachineLearningRoot.prettyPrint() {
     return new MemberPrinter("ML")
         .addNodes("activities", getNumActivity(), getActivityList(),
                   activity -> activity.getIdentifier() + ":\"" + activity.getLabel() + "\"",
@@ -215,7 +214,7 @@ aspect Printing {
   syn String PowerExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " ^ " + getRightOperand().prettyPrint() + ")";
   syn String ParenthesizedLogicalExpression.prettyPrint()  = "(" + getOperand().prettyPrint() + ")";
   syn String NotExpression.prettyPrint()  = "!" + getOperand().prettyPrint();
-  syn String ComparingExpression.prettyPrint() {
+  eq ComparingExpression.prettyPrint() {
     switch (getComparator()) {
       case NotEquals: return "(" + getLeftOperand().prettyPrint() + " != " + getRightOperand().prettyPrint() + ")";
       case Equals: return "(" + getLeftOperand().prettyPrint() + " == " + getRightOperand().prettyPrint() + ")";
@@ -230,4 +229,16 @@ aspect Printing {
   syn String OrExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " | " + getRightOperand().prettyPrint() + ")";
   syn String Designator.prettyPrint() = getItem().getID();
 
+  // Rules
+  eq Rule.prettyPrint() {
+    return new MemberPrinter("Rule")
+        .addIds("TriggeringItems", getObserverList().size(), getObserverList(),
+                  io -> io.observedItem().getID())
+        .addNodes("Condition", getNumCondition(), getConditionList(),
+                  Condition::toString)
+        .addNodes("Action", getNumAction(), getActionList(),
+                  Action::toString)
+        .build();
+  }
+
 }
diff --git a/eraser-base/src/main/jastadd/Resolving.jrag b/eraser-base/src/main/jastadd/Resolving.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..e1d793c1587afcd16b232197a2e87b21c9d6f08b
--- /dev/null
+++ b/eraser-base/src/main/jastadd/Resolving.jrag
@@ -0,0 +1,106 @@
+aspect Resolving {
+
+  //--- resolveThingType ---
+  syn java.util.Optional<ThingType> SmartHomeEntityModel.resolveThingType(String thingTypeId) {
+    for (ThingType thingType : this.getThingTypeList()) {
+      if (thingType.getID().equals(thingTypeId)) {
+        return java.util.Optional.of(thingType);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveChannel ---
+  syn java.util.Optional<Channel> SmartHomeEntityModel.resolveChannel(String channelId) {
+    for (Thing thing : this.getThingList()) {
+      for (Channel channel : thing.getChannelList()) {
+        if (channel.getID().equals(channelId)) {
+          return java.util.Optional.of(channel);
+        }
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveChannelType ---
+  syn java.util.Optional<ChannelType> SmartHomeEntityModel.resolveChannelType(String channelTypeId) {
+    for (ChannelType channelType : this.getChannelTypeList()) {
+      if (channelType.getID().equals(channelTypeId)) {
+        return java.util.Optional.of(channelType);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveItem ---
+  syn java.util.Optional<Item> SmartHomeEntityModel.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);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveGroup ---
+  syn java.util.Optional<Group> SmartHomeEntityModel.resolveGroup(String groupId) {
+    for (Group group : this.getGroupList()) {
+      if (group.getID().equals(groupId)) {
+        return java.util.Optional.of(group);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveMqttTopic ---
+  syn java.util.Optional<MqttTopic> Root.resolveMqttTopic(String mqttTopicId) {
+    return this.getMqttRoot().resolveTopic(mqttTopicId);
+  }
+
+  //--- resolveItemCategory ---
+  syn java.util.Optional<ItemCategory> SmartHomeEntityModel.resolveItemCategory(String categoryName) {
+    for (ItemCategory category : getItemCategoryList()) {
+      if (category.getName().equals(categoryName)) {
+        return java.util.Optional.of(category);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveActivity ---
+  syn java.util.Optional<Activity> Root.resolveActivity(int identifier) {
+    for (Activity activity : getMachineLearningRoot().getActivityList()) {
+      if (activity.getIdentifier() == identifier) {
+        return java.util.Optional.of(activity);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+  syn java.util.Optional<Activity> Root.resolveActivity(String label) {
+    for (Activity activity : getMachineLearningRoot().getActivityList()) {
+      if (activity.getLabel().equals(label)) {
+        return java.util.Optional.of(activity);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveChangeEvent ---
+  syn java.util.Optional<ChangeEvent> Root.resolveChangeEvent(int identifier) {
+    for (ChangeEvent changeEvent : getMachineLearningRoot().getChangeEventList()) {
+      if (changeEvent.getIdentifier() == identifier) {
+        return java.util.Optional.of(changeEvent);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  // implementing resolving for relations
+  refine RefResolverStubs eq StateSyncGroup.resolveTargetItemByToken(String id, int position) {
+    return getRoot().getSmartHomeEntityModel().resolveItem(id).orElseThrow(() -> new RuntimeException("Item '" + id + "' not found!"));
+  }
+
+}
diff --git a/eraser-base/src/main/jastadd/Rules.jrag b/eraser-base/src/main/jastadd/Rules.jrag
index 26c18ae2f1faba258a2db6aa60d5b0c6cf974575..e2acfca41c585dedfc71bd56c0772abcef36d247 100644
--- a/eraser-base/src/main/jastadd/Rules.jrag
+++ b/eraser-base/src/main/jastadd/Rules.jrag
@@ -24,29 +24,20 @@ aspect Rules {
   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;
-        }
+    // 1) Get ItemObserver
+    ItemObserver 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);
     }
-    // 2) Link event and itemObserver
     itemObserver.addTriggeredRule(this);
   }
 
   public void Rule.deactivateFor(Item item) {
-    if (item.hasItemObserver()) {
-        item.getItemObserver().removeTriggeredRule(this);
-    }
+    item.getItemObserver().removeTriggeredRule(this);
 
   }
 
@@ -73,14 +64,6 @@ aspect Rules {
     return executor.scheduleAtFixedRate(() -> trigger(null), initialDelay, period, unit);
   }
 
-  public void Rule.removeActivationOf(Item item) {
-    if (item.hasItemObserver()) {
-      item.getItemObserver().removeTriggeredRule(this);
-    } else {
-      // there is no observer yet
-      logger.warn("Item {} was never activated before.", item);
-    }
-  }
 
   // --- Condition.holdsFor ---
   syn boolean Condition.holdsFor(Item item);
@@ -109,7 +92,11 @@ aspect Rules {
     getAffectedItem().setStateFromString(getNewStateProvider().get());
   }
   public void SetStateFromTriggeringItemAction.applyFor(Item item) {
-    item.copyStateTo(getAffectedItem());
+    Item target = getAffectedItem();
+    if (target==item) {
+      return;
+    }
+    item.copyStateTo(target);
   }
   public void SetStateFromItemsAction.applyFor(Item item) {
     getAffectedItem().setStateFromString(getCombinator().apply(getSourceItems()));
@@ -122,3 +109,18 @@ aspect Rules {
   }
 
 }
+
+aspect StateSyncGroup {
+  rewrite StateSyncGroup {
+    to Rule {
+      Rule rule = new Rule();
+
+      for (Item item : getTargetItemList()) {
+        rule.addAction(new SetStateFromTriggeringItemAction(item));
+        rule.addObserver(item.getItemObserver());
+      }   
+
+      return rule;
+    }
+  }
+}
diff --git a/eraser-base/src/main/jastadd/Rules.relast b/eraser-base/src/main/jastadd/Rules.relast
index 25a0fd2b41f3f1051e03d06ace4f41b6907030e6..22176613130d4fd2802379fffb6d406819a2cd04 100644
--- a/eraser-base/src/main/jastadd/Rules.relast
+++ b/eraser-base/src/main/jastadd/Rules.relast
@@ -28,3 +28,6 @@ MultiplyDoubleToStateAction : SetStateAction ::= <Multiplier:double> ;
 
 ItemObserver ::= ;
 rel ItemObserver.TriggeredRule* <-> Rule.Observer* ;
+
+StateSyncGroup : Rule ;
+rel StateSyncGroup.TargetItem* -> Item;
diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex
index 5f70414fa4005ad1baf1b0623459ce7c5f2f69f2..21987f14a9762c3f7c9de8dadd405ad5431ad7b5 100644
--- a/eraser-base/src/main/jastadd/eraser.flex
+++ b/eraser-base/src/main/jastadd/eraser.flex
@@ -57,6 +57,7 @@ Comment = "//" [^\n\r]+
 "Influx"       { return sym(Terminals.INFLUX); }
 "ML"           { return sym(Terminals.ML); }
 "Rule"         { return sym(Terminals.RULE); }
+"SyncState"    { return sym(Terminals.SYNCSTATE); }
 // special items (group already has a token definition)
 "Activity"     { return sym(Terminals.ACTIVITY); }
 "Color"        { return sym(Terminals.COLOR); }
diff --git a/eraser-base/src/main/jastadd/eraser.parser b/eraser-base/src/main/jastadd/eraser.parser
index 68967a5655bf12cab2e2127db29e1f0ac5ebd808..1a055ec64b0166fa0bea7882e6e0921e7d280976 100644
--- a/eraser-base/src/main/jastadd/eraser.parser
+++ b/eraser-base/src/main/jastadd/eraser.parser
@@ -3,6 +3,7 @@ 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 de.tudresden.inf.st.eraser.jastadd.model.StateSyncGroup;
 import java.util.Map;
 import java.util.HashMap;
 :} ;
@@ -31,6 +32,7 @@ Root goal =
      thing.t goal.r                     {: insertZero(r.getSmartHomeEntityModel().getThingList(), t); return r; :}
   |  item.i goal.r                      {: return r; :}
   |  group.g goal.r                     {: insertZero(r.getSmartHomeEntityModel().getGroupList(), g); return r; :}
+  |  state_sync_group.ss goal.r         {: r.addRule(ss); return r; :}
   |  thing_type.tt goal.r               {: insertZero(r.getSmartHomeEntityModel().getThingTypeList(), tt); return r; :}
   |  parameter goal.r                   {: return r; :}
   |  channel_type.ct goal.r             {: insertZero(r.getSmartHomeEntityModel().getChannelTypeList(), ct); return r; :}
@@ -42,6 +44,7 @@ Root goal =
   |  thing.t                            {: return eph.createRoot(t); :}
   |  item.i                             {: return eph.createRoot(); :}
   |  group.g                            {: return eph.createRoot(g); :}
+  |  state_sync_group.ss                {: return eph.createRoot(ss); :}
   |  thing_type.tt                      {: return eph.createRoot(tt); :}
   |  parameter                          {: return eph.createRoot(); :}
   |  channel_type.ct                    {: return eph.createRoot(ct); :}
@@ -137,6 +140,10 @@ Item item_body =
   |                                     {: return eph.createItem(); :}
   ;
 
+Item item_ref =
+     TEXT.n                                 {: return Item.createRef(n); :}
+  ;
+
 Group group =
      GROUP COLON group_body.gb SEMICOLON    {: return gb; :}
   ;
@@ -154,6 +161,21 @@ Group group_body =
   |                                                   {: return new Group(); :}
   ;
 
+
+StateSyncGroup state_sync_group = SYNCSTATE COLON syncstate_body.ssb SEMICOLON {: return ssb; :};
+
+// SyncState: items=["ITEM_ID", "ITEM_ID"];
+StateSyncGroup syncstate_body =
+     ITEMS EQUALS item_list.items syncstate_body.ss
+     {:
+       for (Item item : items) {
+         ss.addTargetItem(item);
+       }
+       return ss;
+     :}
+  |                                         {: return new StateSyncGroup(); :}
+  ;
+
 ThingType thing_type =
      THING_TYPE COLON thing_type_body.ttb SEMICOLON   {: return ttb; :}
   ;
@@ -284,6 +306,21 @@ StringList string_list_body =
     :}
   ;
 
+ItemList item_list =
+     LB_SQUARE item_list_body.ilb RB_SQUARE         {: return ilb; :}
+  |  LB_SQUARE RB_SQUARE                            {: return new ItemList(); :}
+  ;
+
+ItemList item_list_body =
+     item_ref.ir COMMA item_list_body.ilb           {: ilb.add(ir); return ilb; :}
+  |  item_ref.ir
+    {:
+       ItemList result = new ItemList();
+       result.add(ir);
+       return result;
+    :}
+  ;
+
 StringList round_string_list =
      LB_ROUND round_string_list_body.slb RB_ROUND     {: return slb; :}
   |  LB_ROUND RB_ROUND                                {: return new StringList(); :}
diff --git a/eraser-base/src/main/jastadd/shem.relast b/eraser-base/src/main/jastadd/shem.relast
index ca1878ac6d2722a3e2a1671e066b19c5175c7be6..a71a0ede3307b937a77798d9713ddbae197d323e 100644
--- a/eraser-base/src/main/jastadd/shem.relast
+++ b/eraser-base/src/main/jastadd/shem.relast
@@ -25,7 +25,9 @@ rel Channel.LinkedItem* <-> Item.Channel? ;
 Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ;
 ParameterDefaultValue ::= <Value:String> ;
 
-abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* [ItemObserver] ;
+abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/;
+
+
 rel Item.Category? -> ItemCategory ;
 
 abstract ItemWithBooleanState : Item ::= <_state:boolean> ;
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 795d469bc786cfde73b12f8329956cbb79bb953a..01b91770dc1d4a58dfd4f03033ea1068c9282a98 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
@@ -73,6 +73,7 @@ public class EraserParserHelper {
       // when parsing expressions
       this.root = EraserParserHelper.initialRoot != null ? EraserParserHelper.initialRoot : createRoot();
     }
+
     if (checkUnusedElements) {
       fillUnused();
     }
@@ -93,6 +94,7 @@ public class EraserParserHelper {
     resolveList(itemMap, missingItemLinkListMap, Channel::addLinkedItem);
     resolveList(groupMap, missingSubGroupListMap, Group::addGroup);
     resolveList(itemMap, missingItemListMap, this::addItemToGroup);
+
     resolveList(channelTypeMap, missingChannelTypeListMap, ThingType::addChannelType);
     resolveList(parameterMap, missingParameterListMap, ThingType::addParameter);
     resolveList(itemMap, missingControllingListMap, Item::addControlling);
@@ -103,6 +105,8 @@ public class EraserParserHelper {
     if (checkUnusedElements) {
       checkUnusedElements();
     }
+
+    this.root.treeResolveAll();
   }
 
   private void addItemToGroup(Group group, Item item) {
@@ -292,11 +296,6 @@ public class EraserParserHelper {
     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());
diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java
index a6c89a42bca8e978f837ad805ec4278634f3afa4..53acf15253fcfc3a91067ca332ea8e2f2cdf7642 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java
@@ -80,9 +80,9 @@ public class MemberPrinter {
    * @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);
+  private <T extends ASTNode<?>> void concatIds(Iterable<T> listOfNodes,
+                                             Function<T, String> mapping) {
+    concatNodes(listOfNodes, mapping, true);
   }
 
   /**
@@ -91,7 +91,7 @@ public class MemberPrinter {
    * @param listOfNodes the list of nodes
    * @param mapping a function to map a node to a String
    */
-  private <T extends ASTNode> void concatNodes(Iterable<T> listOfNodes,
+  private <T extends ASTNode<?>> void concatNodes(Iterable<T> listOfNodes,
                                                Function<T, String> mapping,
                                                boolean quote) {
     boolean first = true;
@@ -146,9 +146,9 @@ public class MemberPrinter {
    * @param mapping     A function to map a node to a ModelElement
    * @return this
    */
-  public <T extends ASTNode> MemberPrinter addIds(
+  public <T extends ASTNode<?>> MemberPrinter addIds(
       String name, int count, Iterable<T> listOfNodes,
-      Function<T, ? extends ModelElement> mapping) {
+      Function<T, String> mapping) {
     if (count > 0) {
       sb.append(' ').append(name).append("=[");
       concatIds(listOfNodes, mapping);
@@ -168,7 +168,7 @@ public class MemberPrinter {
    * @param <T>         The type of all nodes
    * @return this
    */
-  public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes,
+  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);
   }
@@ -183,7 +183,7 @@ public class MemberPrinter {
    * @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,
+  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);
@@ -214,7 +214,7 @@ public class MemberPrinter {
    * @param child       The child to append
    * @return this
    */
-  public MemberPrinter addOptionalPrettyPrint(ASTNode child) {
+  public MemberPrinter addOptionalPrettyPrint(ASTNode<?> child) {
     if (child != null) {
       this.empty = false;
       sb.append(child.prettyPrint());
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 fd6024bdfd45e2652eb4eaa118eada3dbfc75f23..3654470a6285e3424d750043b0df66cec295361b 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
@@ -2,6 +2,8 @@ package de.tudresden.inf.st.eraser.util;
 
 import de.tudresden.inf.st.eraser.jastadd.model.*;
 
+import java.util.ArrayList;
+
 /**
  * Helper class to create models used in tests.
  *
@@ -35,6 +37,13 @@ public class TestUtils {
     return ModelAndItem.of(root.getSmartHomeEntityModel(), item);
   }
 
+  public static SmartHomeEntityModel createModelWithGroup() {
+    Root root = Root.createEmptyRoot();
+    ParserUtils.createUnknownGroup(root.getSmartHomeEntityModel(),new ArrayList<>());
+
+    return root.getSmartHomeEntityModel();
+  }
+
   public static NumberItem addItemTo(SmartHomeEntityModel model, double initialValue) {
     return addItemTo(model, initialValue, false);
   }
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 1a1ea62a2d4611fa00da7252510a325fe40cf41f..633627c6bf85e4ddd363865b01bb62155919ae1e 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
@@ -2,12 +2,17 @@ package de.tudresden.inf.st.eraser;
 
 import beaver.Parser;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
+import de.tudresden.inf.st.eraser.jastadd.model.Action;
 import de.tudresden.inf.st.eraser.util.ParserUtils;
 import de.tudresden.inf.st.eraser.util.TestUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Disabled;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
 
 import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ScheduledFuture;
@@ -15,9 +20,9 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.StreamSupport;
 
+import static de.tudresden.inf.st.eraser.util.TestUtils.getDefaultGroup;
 import static org.assertj.core.api.Assertions.fail;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * Testing the simple rule engine.
@@ -205,7 +210,7 @@ public class RulesTest {
     setState(item, 5);
     assertEquals(1, counter.get(item), "Change of item state should trigger the rule");
 
-    rule.removeActivationOf(item);
+    rule.deactivateFor(item);
 
     setState(item, 3);
     assertEquals(1, counter.get(item), "Change of item state should not change the counter anymore");
@@ -245,6 +250,254 @@ public class RulesTest {
     assertEquals(2, counter.get(item), "Change of item to 7 should not trigger the rule, check2 violated");
   }
 
+  @Test
+  public void testStateSyncGroupRewriteStructure() {
+    // init StateSyncGroup
+    StateSyncGroup group = new StateSyncGroup();
+
+    ArrayList<Item> items = new ArrayList<>();
+
+    // create model and an item
+    TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0, true);
+    NumberItem item0 = mai.item;
+    group.addTargetItem(item0);
+    items.add(item0);
+
+    // init more items
+    for (int i=1;i<=4;i++) {
+      NumberItem item = TestUtils.addItemTo(mai.model, i, true);
+      item.setID("item"+i);
+      group.addTargetItem(item);
+      items.add(item);
+    }
+
+    // add StateSyncGroup as rule to root
+    mai.model.getRoot().addRule(group);
+
+
+
+    // trace rewritten rule and its actions
+    Rule rewrittenRule = mai.model.getRoot().getRules().getChild(0);
+
+
+    ImmutableList<Action> actions = ImmutableList.copyOf(rewrittenRule.getActionList().iterator());
+    ImmutableList<ItemObserver> observers = ImmutableList.copyOf(rewrittenRule.getObservers().iterator());
+
+    // check general structure
+    assertEquals(ImmutableList.copyOf(rewrittenRule.getConditionList().iterator()).size(), 0);
+    assertEquals(actions.size(), 5);
+
+    // check actions and observers
+    for (int i=0;i<items.size();i++) {
+      assertTrue(actions.get(i) instanceof SetStateFromTriggeringItemAction);
+      assertTrue(observers.contains(items.get(i).getItemObserver()));
+    }
+
+
+
+  }
+
+  private static void addItemToModel(SmartHomeEntityModel model, Item item) {
+    getDefaultGroup(model).addItem(item);
+  }
+
+  @Test
+  public void testColorItemStateSyncGroup() {
+    StateSyncGroup group = new StateSyncGroup();
+
+    //init model and 3 items
+    SmartHomeEntityModel model = TestUtils.createModelWithGroup();
+
+    ColorItem colorItem1 = new ColorItem();
+    addItemToModel(model,colorItem1);
+    group.addTargetItem(colorItem1);
+
+    ColorItem colorItem2 = new ColorItem();
+    addItemToModel(model,colorItem2);
+    group.addTargetItem(colorItem2);
+
+    ColorItem colorItem3 = new ColorItem();
+    addItemToModel(model,colorItem3);
+    group.addTargetItem(colorItem3);
+
+
+    // add StateSyncGroup as rule to root and trigger rewrite
+    model.getRoot().addRule(group);
+    model.getRoot().doFullTraversal();
+
+
+    colorItem1.setState(TupleHSB.parse("0,0,100"));
+    assertEquals(colorItem1.getState(),TupleHSB.parse("0,0,100"));
+    assertEquals(colorItem2.getState(),TupleHSB.parse("0,0,100"));
+    assertEquals(colorItem3.getState(),TupleHSB.parse("0,0,100"));
+
+    colorItem3.setState(TupleHSB.parse("0,0,7"));
+    assertEquals(colorItem1.getState(),TupleHSB.parse("0,0,7"));
+    assertEquals(colorItem2.getState(),TupleHSB.parse("0,0,7"));
+    assertEquals(colorItem3.getState(),TupleHSB.parse("0,0,7"));
+
+
+  }
+
+  @Test
+  public void testDateTimeItemStateSyncGroup() {
+    StateSyncGroup group = new StateSyncGroup();
+
+    //init model and 3 items
+    SmartHomeEntityModel model = TestUtils.createModelWithGroup();
+
+    DateTimeItem dateTimeItem1 = new DateTimeItem();
+    addItemToModel(model,dateTimeItem1);
+    group.addTargetItem(dateTimeItem1);
+
+    DateTimeItem dateTimeItem2 = new DateTimeItem();
+    addItemToModel(model,dateTimeItem2);
+    group.addTargetItem(dateTimeItem2);
+
+    DateTimeItem dateTimeItem3 = new DateTimeItem();
+    addItemToModel(model,dateTimeItem3);
+    group.addTargetItem(dateTimeItem3);
+
+
+    // add StateSyncGroup as rule to root and trigger rewrite
+    model.getRoot().addRule(group);
+    model.getRoot().doFullTraversal();
+
+    Instant i1 = Instant.now();
+    dateTimeItem1.setState(i1);
+    assertEquals(dateTimeItem1.getState(),i1);
+    assertEquals(dateTimeItem2.getState(),i1);
+    assertEquals(dateTimeItem3.getState(),i1);
+
+    Instant i2 = Instant.now();
+    dateTimeItem3.setState(i2);
+    assertEquals(dateTimeItem1.getState(),i2);
+    assertEquals(dateTimeItem2.getState(),i2);
+    assertEquals(dateTimeItem3.getState(),i2);
+  }
+
+  /**
+   * Also for DimmerItem, RollerShutterItem, ActivityItem
+   */
+  @Test
+  public void testDoubleStateItemStateSyncGroup() {
+    StateSyncGroup group = new StateSyncGroup();
+
+    //init model and 3 items
+    SmartHomeEntityModel model = TestUtils.createModelWithGroup();
+
+    NumberItem numberItem1 = new NumberItem();
+    addItemToModel(model,numberItem1);
+    group.addTargetItem(numberItem1);
+
+    NumberItem numberItem2 = new NumberItem();
+    addItemToModel(model,numberItem2);
+    group.addTargetItem(numberItem2);
+
+    NumberItem numberItem3 = new NumberItem();
+    addItemToModel(model,numberItem3);
+    group.addTargetItem(numberItem3);
+
+
+    // add StateSyncGroup as rule to root and trigger rewrite
+    model.getRoot().addRule(group);
+    model.getRoot().doFullTraversal();
+
+
+    numberItem1.setState(123);
+    assertEquals(numberItem1.getState(),123);
+    assertEquals(numberItem2.getState(),123);
+    assertEquals(numberItem3.getState(),123);
+
+    numberItem2.setState(42);
+    assertEquals(numberItem1.getState(),42);
+    assertEquals(numberItem2.getState(),42);
+    assertEquals(numberItem3.getState(),42);
+  }
+
+  /**
+   * Also for ImageItem, LocationItem, PlayerItem, DefaultItem
+   */
+  @Test
+  public void testStringStateItemStateSyncGroup() {
+    StateSyncGroup group = new StateSyncGroup();
+
+    //init model and 3 items
+    SmartHomeEntityModel model = TestUtils.createModelWithGroup();
+
+    StringItem stringItem1 = new StringItem();
+    addItemToModel(model,stringItem1);
+    group.addTargetItem(stringItem1);
+
+    StringItem stringItem2 = new StringItem();
+    addItemToModel(model,stringItem2);
+    group.addTargetItem(stringItem2);
+
+    StringItem stringItem3 = new StringItem();
+    addItemToModel(model,stringItem3);
+    group.addTargetItem(stringItem3);
+
+
+    // add StateSyncGroup as rule to root and trigger rewrite
+    model.getRoot().addRule(group);
+    model.getRoot().doFullTraversal();
+
+
+    stringItem1.setState("123");
+    assertEquals(stringItem1.getState(),"123");
+    assertEquals(stringItem2.getState(),"123");
+    assertEquals(stringItem3.getState(),"123");
+
+    stringItem2.setState("Hermes");
+    assertEquals(stringItem1.getState(),"Hermes");
+    assertEquals(stringItem2.getState(),"Hermes");
+    assertEquals(stringItem3.getState(),"Hermes");
+
+
+  }
+
+
+  /**
+   * Also for ContactItem
+   */
+  @Test
+  public void testBooleanStateItemStateSyncGroup() {
+    StateSyncGroup group = new StateSyncGroup();
+
+    //init model and 3 items
+    SmartHomeEntityModel model = TestUtils.createModelWithGroup();
+
+    SwitchItem switchItem1 = new SwitchItem();
+    addItemToModel(model,switchItem1);
+    group.addTargetItem(switchItem1);
+
+    SwitchItem switchItem2 = new SwitchItem();
+    addItemToModel(model,switchItem2);
+    group.addTargetItem(switchItem2);
+
+    SwitchItem switchItem3 = new SwitchItem();
+    addItemToModel(model,switchItem3);
+    group.addTargetItem(switchItem3);
+
+
+    // add StateSyncGroup as rule to root and trigger rewrite
+    model.getRoot().addRule(group);
+    model.getRoot().doFullTraversal();
+
+
+    switchItem3.setState(false);
+    assertFalse(switchItem1.getState());
+    assertFalse(switchItem2.getState());
+    assertFalse(switchItem3.getState());
+
+    switchItem1.setState(true);
+    assertTrue(switchItem1.getState());
+    assertTrue(switchItem2.getState());
+    assertTrue(switchItem3.getState());
+
+  }
+
+
   @Test
   public void testTwoActions() {
     TestUtils.ModelAndItem modelAndItem = createModelAndItem(2);
@@ -673,7 +926,7 @@ public class RulesTest {
 
   private StringItem addStringItem(SmartHomeEntityModel model, String initialValue) {
     StringItem item = new StringItem();
-    Group group = TestUtils.getDefaultGroup(model);
+    Group group = getDefaultGroup(model);
     item.setID("item" + group.getNumItem());
     item.setState(initialValue, false);
     group.addItem(item);