From 08227a2fae4861136572ea159823d6dc859199bd Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Fri, 12 Apr 2019 15:23:37 +0200
Subject: [PATCH] Refactoring to (hopefully now) correct version of mqtt and
 some cleanup.

- Added HandlerPhase for better thing status updates
- Do not use MqttService anymore, instead register for thing changes and react, if broker is found
- Working on EraserHandler, not fully working yet
- Remove date on binding xml, as updating the bundle works reliably now
---
 ESH-INF/binding/binding.xml                   |   2 +-
 ESH-INF/thing/thing-types.xml                 |   6 +
 META-INF/MANIFEST.MF                          |   4 +
 .../binding/openlicht/BindingConstants.java   |   1 +
 .../handler/AbstractMqttHandler.java          | 256 +++++++++++-------
 .../handler/AbstractSmartphoneHandler.java    |   4 +
 .../openlicht/handler/EraserHandler.java      | 226 ++++++++++++++--
 .../openlicht/handler/HandlerPhase.java       |   8 +
 .../binding/openlicht/handler/MqttUtils.java  |   7 -
 .../handler/SkyWriterHATHandler.java          |   5 +
 .../openlicht/handler/UpdateState.java        |  10 +
 .../internal/ConfigurationHolder.java         |   4 +-
 .../DelegateEraserRegistryChangeListener.java |  30 ++
 .../internal/OpenLichtHandlerFactory.java     |  38 +--
 14 files changed, 447 insertions(+), 154 deletions(-)
 create mode 100644 src/main/java/org/openhab/binding/openlicht/handler/HandlerPhase.java
 create mode 100644 src/main/java/org/openhab/binding/openlicht/handler/UpdateState.java
 create mode 100644 src/main/java/org/openhab/binding/openlicht/internal/DelegateEraserRegistryChangeListener.java

diff --git a/ESH-INF/binding/binding.xml b/ESH-INF/binding/binding.xml
index c23ad97..f94ecb6 100644
--- a/ESH-INF/binding/binding.xml
+++ b/ESH-INF/binding/binding.xml
@@ -4,7 +4,7 @@
 	xsi:schemaLocation="http://eclipse.org/smarthome/schemas/binding/v1.0.0 http://eclipse.org/smarthome/schemas/binding-1.0.0.xsd">
 
 	<name>OpenLicht Binding</name>
-	<description>This is the binding for OpenLicht (Last Update: 2019-04-09 15:21).</description>
+	<description>This is the binding for OpenLicht.</description>
 	<author>René Schöne</author>
 
 </binding:binding>
diff --git a/ESH-INF/thing/thing-types.xml b/ESH-INF/thing/thing-types.xml
index d842b36..a2f7099 100644
--- a/ESH-INF/thing/thing-types.xml
+++ b/ESH-INF/thing/thing-types.xml
@@ -8,6 +8,7 @@
     <thing-type id="eraser">
        <label>Eraser</label>
        <description>Establish communication with a running eraser via MQTT</description>
+
         <config-description>
 	        <parameter name="brokerName" type="text" required="true">
 	            <label>Broker Name</label>
@@ -30,6 +31,11 @@
 	            <description>MQTT topic prefix for messages leaving openHAB to eraser.</description>
 	            <default>oh/out/</default>
             </parameter>
+            <parameter name="publish-group" type="text" required="true">
+		        <label>Publish group</label>
+		        <description>Group whose member will trigger an update</description>
+		        <context>item</context>
+            </parameter>
         </config-description>
     </thing-type>
 
diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
index baaa6f9..dcaa926 100644
--- a/META-INF/MANIFEST.MF
+++ b/META-INF/MANIFEST.MF
@@ -9,9 +9,11 @@ Bundle-Vendor: openHAB
 Bundle-Version: 2.3.0.qualifier
 Import-Package: 
  org.eclipse.jdt.annotation;resolution:=optional,
+ org.eclipse.smarthome.binding.mqtt.handler,
  org.eclipse.smarthome.config.core,
  org.eclipse.smarthome.core.common.registry,
  org.eclipse.smarthome.core.events,
+ org.eclipse.smarthome.core.items,
  org.eclipse.smarthome.core.items.events,
  org.eclipse.smarthome.core.library.types,
  org.eclipse.smarthome.core.thing,
@@ -22,7 +24,9 @@ Import-Package:
  org.eclipse.smarthome.io.transport.mqtt,
  org.osgi.framework,
  org.osgi.service.component,
+ org.osgi.service.component.annotations;version="[1.3.0,2.0.0)";resolution:=optional,
  org.slf4j
 Service-Component: OSGI-INF/*.xml
 Export-Package: org.openhab.binding.openlicht,
  org.openhab.binding.openlicht.handler
+Automatic-Module-Name: org.openhab.binding.openlicht
diff --git a/src/main/java/org/openhab/binding/openlicht/BindingConstants.java b/src/main/java/org/openhab/binding/openlicht/BindingConstants.java
index 7673c99..aa9fb76 100644
--- a/src/main/java/org/openhab/binding/openlicht/BindingConstants.java
+++ b/src/main/java/org/openhab/binding/openlicht/BindingConstants.java
@@ -32,6 +32,7 @@ public class BindingConstants {
     public static final String CONFIG_BYTE_BASED_MESSAGES = "byte-based-messages";
     public static final String CONFIG_TIMEOUT_MQTT_UNSUPPORTED_CATEGORIES = "unsupported-category-reset";
     public static final String CONFIG_ERASER_OUT_TOPIC = "outTopic";
+    public static final String CONFIG_ERASER_PUBLISH_GROUP = "publish-group";
 
     // List of all Thing Type UIDs
     public static final ThingTypeUID THING_TYPE_SKYWRITER_HAT = new ThingTypeUID(BINDING_ID, "skywriter-hat");
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/AbstractMqttHandler.java b/src/main/java/org/openhab/binding/openlicht/handler/AbstractMqttHandler.java
index a16ebb5..4f2f186 100644
--- a/src/main/java/org/openhab/binding/openlicht/handler/AbstractMqttHandler.java
+++ b/src/main/java/org/openhab/binding/openlicht/handler/AbstractMqttHandler.java
@@ -1,26 +1,25 @@
 package org.openhab.binding.openlicht.handler;
 
 import static org.openhab.binding.openlicht.BindingConstants.*;
+import static org.openhab.binding.openlicht.handler.HandlerPhase.*;
 
 import java.math.BigDecimal;
-import java.util.Map.Entry;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.binding.mqtt.handler.AbstractBrokerHandler;
+import org.eclipse.smarthome.core.common.registry.RegistryChangeListener;
 import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingRegistry;
 import org.eclipse.smarthome.core.thing.ThingStatus;
 import org.eclipse.smarthome.core.thing.ThingStatusDetail;
 import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
-//import org.openhab.binding.mqtt.handler.AbstractBrokerHandler;
-//import org.openhab.binding.openlicht.internal.ConfigurationHolder;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
 import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
 import org.eclipse.smarthome.io.transport.mqtt.MqttMessageSubscriber;
-import org.eclipse.smarthome.io.transport.mqtt.MqttService;
 import org.eclipse.smarthome.io.transport.mqtt.MqttServiceObserver;
 import org.openhab.binding.openlicht.internal.ConfigurationHolder;
 import org.osgi.framework.Version;
@@ -28,11 +27,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public abstract class AbstractMqttHandler extends BaseThingHandler
-        implements MqttMessageSubscriber, MqttServiceObserver/* , RegistryChangeListener<Thing> */ {
+        implements MqttMessageSubscriber, MqttServiceObserver, RegistryChangeListener<Thing> {
 
-    protected final @NonNull Logger logger = LoggerFactory.getLogger(AbstractMqttHandler.class);
-    // private @Nullable ThingRegistry thingRegistry;
-    private @Nullable MqttService mqttService;
+    private final @NonNull Logger logger = LoggerFactory.getLogger(AbstractMqttHandler.class);
+    private @Nullable ThingRegistry thingRegistry;
+    // private @Nullable MqttService mqttService;
     private int usedTopicLength = 0;
     private MqttBrokerConnection currentBrokerConnection = null;
     private boolean warnNoBrokerConnection = true;
@@ -44,44 +43,49 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
 
     public AbstractMqttHandler(Thing thing, ConfigurationHolder configurationHolder) {
         super(thing);
-        // this.thingRegistry = configurationHolder.getThingRegistry();
-        // this.thingRegistry.addRegistryChangeListener(this);
-        this.mqttService = configurationHolder.getMqttService();
-        if (this.mqttService != null) {
-            this.mqttService.addBrokersListener(this);
+        this.thingRegistry = configurationHolder.getThingRegistry();
+        if (this.thingRegistry != null) {
+            this.thingRegistry.addRegistryChangeListener(this);
         } else {
-            logger.warn("No mqtt service, so no broker listener added");
+            logger.warn("No Thing registry, so no updated broker");
         }
+        // this.mqttService = configurationHolder.getMqttService();
+        // if (this.mqttService != null) {
+        // this.mqttService.addBrokersListener(this);
+        // } else {
+        // logger.warn("No mqtt service, so no broker listener added");
+        // }
         this.executor = configurationHolder.getExecutor();
         this.version = configurationHolder.getVersion();
         this.configUpdateLock = new ReentrantLock();
         logger.info("Started handler " + this.getClass().getSimpleName() + " in version " + version);
     }
 
-    // @Override
-    // public void added(Thing element) {
-    // if (thingIsMyMqttBroker(element)) {
-    // addConnectionOf(element);
-    // }
-    // }
-    //
-    // @Override
-    // public void removed(Thing element) {
-    // if (thingIsMyMqttBroker(element)) {
-    // removeMyBroker();
-    // }
-    // }
-    //
-    // @Override
-    // public void updated(Thing oldElement, Thing element) {
-    // if (thingIsMyMqttBroker(element)) {
-    // removeMyBroker();
-    // addConnectionOf(element);
-    // }
-    // }
+    @Override
+    public void added(Thing element) {
+        if (thingIsMyMqttBroker(element)) {
+            addConnectionOf(element);
+        }
+    }
+
+    @Override
+    public void removed(Thing element) {
+        if (thingIsMyMqttBroker(element)) {
+            removeMyBroker();
+        }
+    }
+
+    @Override
+    public void updated(Thing oldElement, Thing element) {
+        if (thingIsMyMqttBroker(element)) {
+            removeMyBroker();
+            addConnectionOf(element);
+        }
+    }
 
     @Override
     public void brokerAdded(String brokerID, MqttBrokerConnection broker) {
+        logger.debug("{} checking new broker '{}'", me(), brokerID);
         if (brokerID.equals(myBrokerName)) {
             myBrokerAdded(broker);
         }
@@ -94,60 +98,68 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
         }
     }
 
-    // private boolean thingIsMyMqttBroker(Thing thing) {
-    // if (!"mqtt:systemBroker".equals(thing.getThingTypeUID().getAsString())
-    // && !"mqtt:broker".equals(thing.getThingTypeUID().getAsString())) {
-    // return false;
-    // }
-    // return myBrokerName.equals(thing.getUID().getAsString());
-    // }
+    private boolean thingIsMyMqttBroker(Thing thing) {
+        if (myBrokerName == null) {
+            logger.warn("Broker name of {} not set", me());
+            return false;
+        }
+        if (!"mqtt:systemBroker".equals(thing.getThingTypeUID().getAsString())
+                && !"mqtt:broker".equals(thing.getThingTypeUID().getAsString())) {
+            return false;
+        }
+        return myBrokerName.equals(thing.getLabel());
+    }
 
-    // private void addConnectionOf(Thing mqttBroker) {
-    // ThingHandler handler = mqttBroker.getHandler();
-    // this.mqttService.addBrokerConnection(myBrokerName, currentBrokerConnection)
-    // if (handler instanceof AbstractBrokerHandler) {
-    // AbstractBrokerHandler abh = (AbstractBrokerHandler) handler;
-    // myBrokerAdded(abh.getConnection());
-    // }
-    // }
+    private void addConnectionOf(Thing mqttBroker) {
+        ThingHandler handler = mqttBroker.getHandler();
+        // this.mqttService.addBrokerConnection(myBrokerName, currentBrokerConnection);
+        if (handler instanceof AbstractBrokerHandler) {
+            AbstractBrokerHandler abh = (AbstractBrokerHandler) handler;
+            myBrokerAdded(abh.getConnection());
+        }
+    }
 
     @Override
     public void initialize() {
-        updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, "Searching broker");
+        statusUpdate(INIT, true, "Searching broker");
         new Thread(() -> {
             try {
+                this.logger.debug("Initializing {}", me());
                 this.configUpdateLock.lock();
-                this.logger.debug("Initializing");
+                moreInitializeBefore();
                 this.byteBaseMessages = getConfigValueAsBoolean(CONFIG_BYTE_BASED_MESSAGES);
                 String brokerName = getConfigValueAsString(CONFIG_BROKER_NAME);
                 if (brokerName == null) {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "BrokerName not set");
+                    statusUpdate(CONFIGURATION, false, "BrokerName not set");
                     return;
                 }
                 this.myBrokerName = brokerName;
-                logger.debug("Setting myBrokerName to '{}'", myBrokerName);
+                logger.debug("Setting myBrokerName to '{}' for {}", myBrokerName, me());
 
-                // for (Thing thing : this.thingRegistry.getAll()) {
-                // if (thingIsMyMqttBroker(thing)) {
-                // addConnectionOf(thing);
-                // }
-                // }
-                MqttService service = this.mqttService;
-                if (service == null) {
-                    logger.debug("No mqtt service yet");
-                } else {
-                    for (Entry<@NonNull String, @NonNull MqttBrokerConnection> entry : service.getAllBrokerConnections()
-                            .entrySet()) {
-                        brokerAdded(entry.getKey(), entry.getValue());
+                if (this.thingRegistry != null) {
+                    for (Thing thing : this.thingRegistry.getAll()) {
+                        if (thingIsMyMqttBroker(thing)) {
+                            addConnectionOf(thing);
+                        }
                     }
+                } else {
+                    logger.warn("No thing registry, thus no broker connection");
                 }
+                // MqttService service = this.mqttService;
+                // if (service == null) {
+                // logger.warn("No mqtt service yet for {}", me());
+                // } else {
+                // for (Entry<@NonNull String, @NonNull MqttBrokerConnection> entry : service.getAllBrokerConnections()
+                // .entrySet()) {
+                // brokerAdded(entry.getKey(), entry.getValue());
+                // }
+                // }
                 if (this.currentBrokerConnection == null) {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
-                            "Broker with name '" + this.myBrokerName + "' not found.");
-                } else {
-                    updateStatus(ThingStatus.ONLINE);
+                    statusUpdate(COMMUNICATION, false,
+                            "Broker with name '" + this.myBrokerName + "' not found for " + me());
                 }
-                moreInitialize();
+                moreInitializeAfter();
+                logger.debug("Finished initialization of {}", me());
             } finally {
                 this.configUpdateLock.unlock();
             }
@@ -168,27 +180,23 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
     }
 
     public void myBrokerAdded(MqttBrokerConnection broker) {
-        this.logger.info("Got correct broker, subscribing to topic {}", getTopic());
+        this.logger.info("Got correct broker {}, subscribing to topic {} for {}", broker, getTopic(), me());
         currentBrokerConnection = broker;
         warnNoBrokerConnection = true;
         updateTopicLength();
-        CompletableFuture<@NonNull Boolean> future = null;
-        try {
-            currentBrokerConnection.start();
-            if (subscribeTopic()) {
-                future = broker.subscribe(getTopic(), this);
-            }
-            if (future != null && future.get()) {
-                updateStatus(ThingStatus.ONLINE);
-                this.logger.info("Broker found, thing is online");
-            }
-        } catch (InterruptedException e) {
-            this.logger.debug("Interrupted while waiting for connection");
-        } catch (ExecutionException e) {
-            String message = "Could not add subscriber to broker";
-            this.logger.error(message);
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
-            this.logger.warn("exception", e);
+        String description = "Subscribing to topic " + getTopic() + " at " + broker;
+        statusUpdate(CONFIGURATION, true, description);
+        currentBrokerConnection.start();
+        if (subscribeTopic()) {
+            broker.subscribe(getTopic(), this).whenComplete((result, exception) -> {
+                if (exception != null) {
+                    this.logger.warn("exception", exception);
+                    statusUpdate(COMMUNICATION, false, exception.getMessage());
+                    return;
+                }
+                this.logger.info("Broker found for {}, thing is online", me());
+                statusUpdate(FINISH, true, "Finished");
+            });
         }
     }
 
@@ -199,10 +207,21 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
     }
 
     public void removeMyBroker() {
-        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Broker is offline");
-        if (this.mqttService != null) {
-            this.mqttService.removeBrokerConnection(myBrokerName);
+        statusUpdate(COMMUNICATION, false, "Broker is offline");
+        try {
+            if (currentBrokerConnection != null) {
+                currentBrokerConnection.unsubscribe(getTopic(), this);
+            }
+        } finally {
+            currentBrokerConnection = null;
+            logger.debug("Removing broker of {}", me());
         }
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        logger.debug("Disposing " + this);
         currentBrokerConnection = null;
     }
 
@@ -210,7 +229,7 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
         if (currentBrokerConnection == null) {
             if (warnNoBrokerConnection) {
                 // just report once
-                logger.warn("Can't publish message, no connection");
+                logger.warn("Can't publish message in {}, no connection", me());
                 warnNoBrokerConnection = false;
             }
             return;
@@ -230,10 +249,53 @@ public abstract class AbstractMqttHandler extends BaseThingHandler
         return this.usedTopicLength;
     }
 
+    protected String me() {
+        return this.getClass().getSimpleName() + ":" + getThing().getUID().getAsString();
+    }
+
+    /**
+     * Updates the state of the managed thing. Subclasses may override this to add special handling.
+     *
+     * @param phase       Phase of the thing configuration
+     * @param success     Whether the phase was successful
+     * @param description Detailed description of the status
+     */
+    protected void statusUpdate(HandlerPhase phase, boolean success, String description) {
+        switch (phase) {
+            case INIT:
+                updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, description);
+                break;
+            case CONFIGURATION:
+                if (success) {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, description);
+                } else {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
+                }
+                break;
+            case COMMUNICATION:
+                if (success) {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, description);
+                } else {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
+                }
+                break;
+            case FINISH:
+                updateStatus(ThingStatus.ONLINE);
+                break;
+        }
+    }
+
+    /**
+     * Subclasses may override this to do more asynchronous initialization before normal initialization.
+     */
+    protected void moreInitializeBefore() {
+        // empty by default
+    }
+
     /**
-     * Subclasses may override this to do more asynchronous initialization.
+     * Subclasses may override this to do more asynchronous initialization after normal initialization.
      */
-    protected void moreInitialize() {
+    protected void moreInitializeAfter() {
         // empty by default
     }
 
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/AbstractSmartphoneHandler.java b/src/main/java/org/openhab/binding/openlicht/handler/AbstractSmartphoneHandler.java
index 8c1fd5d..52f92aa 100644
--- a/src/main/java/org/openhab/binding/openlicht/handler/AbstractSmartphoneHandler.java
+++ b/src/main/java/org/openhab/binding/openlicht/handler/AbstractSmartphoneHandler.java
@@ -23,6 +23,7 @@ import org.eclipse.smarthome.core.library.types.DecimalType;
 import org.eclipse.smarthome.core.thing.ChannelUID;
 import org.eclipse.smarthome.core.thing.Thing;
 import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
 import org.openhab.binding.openlicht.internal.ConfigurationHolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,6 +47,9 @@ public abstract class AbstractSmartphoneHandler extends AbstractMqttHandler {
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            return;
+        }
         logger.info("Got command for read-only thing: {} {}", channelUID.getAsString(), command.toFullString());
     }
 
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/EraserHandler.java b/src/main/java/org/openhab/binding/openlicht/handler/EraserHandler.java
index d2c97a7..704471f 100644
--- a/src/main/java/org/openhab/binding/openlicht/handler/EraserHandler.java
+++ b/src/main/java/org/openhab/binding/openlicht/handler/EraserHandler.java
@@ -1,78 +1,246 @@
 package org.openhab.binding.openlicht.handler;
 
-import java.util.Collections;
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.smarthome.core.events.Event;
-import org.eclipse.smarthome.core.events.EventFilter;
-import org.eclipse.smarthome.core.events.EventPublisher;
-import org.eclipse.smarthome.core.events.EventSubscriber;
-import org.eclipse.smarthome.core.items.events.ItemEventFactory;
-import org.eclipse.smarthome.core.items.events.ItemStateEvent;
+import org.eclipse.smarthome.core.items.GenericItem;
+import org.eclipse.smarthome.core.items.GroupItem;
+import org.eclipse.smarthome.core.items.Item;
+import org.eclipse.smarthome.core.items.ItemNotFoundException;
+import org.eclipse.smarthome.core.items.ItemRegistry;
+import org.eclipse.smarthome.core.items.StateChangeListener;
+import org.eclipse.smarthome.core.library.types.DateTimeType;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.HSBType;
+import org.eclipse.smarthome.core.library.types.OnOffType;
 import org.eclipse.smarthome.core.library.types.StringType;
 import org.eclipse.smarthome.core.thing.ChannelUID;
 import org.eclipse.smarthome.core.thing.Thing;
 import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.State;
 import org.openhab.binding.openlicht.BindingConstants;
 import org.openhab.binding.openlicht.internal.ConfigurationHolder;
+import org.openhab.binding.openlicht.internal.DelegateEraserRegistryChangeListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class EraserHandler extends AbstractMqttHandler implements EventSubscriber {
+public class EraserHandler extends AbstractMqttHandler implements StateChangeListener {
 
-    private @Nullable EventPublisher eventPublisher;
+    private final @NonNull Logger logger = LoggerFactory.getLogger(EraserHandler.class);
+    private @Nullable ItemRegistry itemRegistry;
     private String outTopic;
+    private String publishGroupName;
+    private boolean publishGroupFound = false;
+    private Set<GenericItem> observedItems = new HashSet<>();
+    private WeakReference<DelegateEraserRegistryChangeListener> delegateListener;
+    private HandlerPhase lastStatusUpdatePhase;
+    private boolean lastStatusUpdateSuccess;
+    private String lastStatusUpdateDescription;
 
     public EraserHandler(Thing thing, ConfigurationHolder configurationHolder) {
         super(thing, configurationHolder);
-        this.eventPublisher = configurationHolder.getEventPublisher();
+        this.itemRegistry = configurationHolder.getItemRegistry();
+        ItemRegistry registry = this.itemRegistry;
+        if (registry != null) {
+            delegateListener = new WeakReference<DelegateEraserRegistryChangeListener>(
+                    new DelegateEraserRegistryChangeListener(this));
+            registry.addRegistryChangeListener(delegateListener.get());
+        }
     }
 
     @Override
-    protected void moreInitialize() {
+    protected void moreInitializeBefore() {
         this.outTopic = getConfigValueAsString(BindingConstants.CONFIG_ERASER_OUT_TOPIC);
+        this.publishGroupName = getConfigValueAsString(BindingConstants.CONFIG_ERASER_PUBLISH_GROUP);
         if (this.outTopic.charAt(this.outTopic.length() - 1) != '/') {
             this.outTopic += "/";
         }
+        if (this.itemRegistry != null) {
+            try {
+                Item publishGroupItem = this.itemRegistry.getItem(this.publishGroupName);
+                myGroupFound(publishGroupItem);
+            } catch (ItemNotFoundException e) {
+                this.publishGroupFound = false;
+                logger.warn("Could not find group with name '" + this.publishGroupName + "'");
+            }
+        } else {
+            logger.warn("No item registry set, so no updates for item changes");
+        }
+    }
+
+    private void myGroupFound(Item publishGroupItem) {
+        if (publishGroupItem instanceof GroupItem) {
+            this.publishGroupFound = true;
+            GroupItem groupItem = (GroupItem) publishGroupItem;
+            for (Item member : groupItem.getAllMembers()) {
+                added(member);
+            }
+        } else {
+            logger.warn("Found an item with name {}, but was not a generic item", this.publishGroupName);
+        }
+    }
+
+    @Override
+    protected void statusUpdate(HandlerPhase phase, boolean success, String description) {
+        this.lastStatusUpdatePhase = phase;
+        this.lastStatusUpdateSuccess = success;
+        this.lastStatusUpdateDescription = description;
+        String newDescription = description;
+        if (!this.publishGroupFound) {
+            // append information to description
+            newDescription = description + (success ? ", but" : " and") + " group to publish with name '"
+                    + this.publishGroupName + "' not found.";
+        }
+        switch (phase) {
+            case INIT:
+                super.statusUpdate(phase, success, description);
+                break;
+            case CONFIGURATION:
+            case COMMUNICATION:
+                super.statusUpdate(phase, success, newDescription);
+                break;
+            case FINISH:
+                if (this.publishGroupFound) {
+                    super.statusUpdate(phase, success, description);
+                } else {
+                    super.statusUpdate(HandlerPhase.CONFIGURATION, false, newDescription);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        if (this.itemRegistry != null) {
+            this.itemRegistry.removeRegistryChangeListener(delegateListener.get());
+            delegateListener.clear();
+        }
+        for (GenericItem observedItem : observedItems) {
+            observedItem.removeStateChangeListener(this);
+        }
     }
 
     @Override
     public void processMessage(String topic, byte[] payload) {
         // check topic, and forward payload to respective item
+        logger.debug("Got message, in topc: '" + topic + "'");
         int indexOfLastSlash = topic.lastIndexOf('/');
-        String itemName = topic.substring(indexOfLastSlash);
-        ItemStateEvent eshEvent = ItemEventFactory.createStateEvent(itemName, new StringType(new String(payload)));
-        EventPublisher publisher = this.eventPublisher;
-        if (publisher == null) {
-            logger.debug("No event publisher to process message");
+        String itemName = topic.substring(indexOfLastSlash + 1);
+        logger.debug("Using item name: '" + itemName + "'");
+        ItemRegistry registry = this.itemRegistry;
+        if (registry != null) {
+            Item item;
+            try {
+                item = registry.getItem(itemName);
+            } catch (ItemNotFoundException e) {
+                logger.debug("Item with name '" + itemName + "' not found.");
+                return;
+            }
+            // assume this is a GenericItem
+            GenericItem genericItem = (GenericItem) item;
+            State newState = setState(genericItem, new String(payload));
+            if (newState != null) {
+                genericItem.setState(newState);
+            } else {
+                logger.warn("Could not set state for {} using '{}'", genericItem, new String(payload));
+            }
+            genericItem.setState(new StringType(new String(payload)));
         } else {
-            publisher.post(eshEvent);
+            logger.debug("No item registry to process message");
+        }
+    }
+
+    private State setState(GenericItem genericItem, String state) {
+        for (Class<? extends @NonNull State> type : genericItem.getAcceptedDataTypes()) {
+            if (type.equals(StringType.class)) {
+                return new StringType(state);
+            } else if (type.equals(DecimalType.class)) {
+                return new DecimalType(state);
+            } else if (type.equals(OnOffType.class)) {
+                return OnOffType.from(state);
+            } else if (type.equals(DateTimeType.class)) {
+                return new DateTimeType(state);
+            } else if (type.equals(HSBType.class)) {
+                return new HSBType(state);
+            }
         }
+        return null;
     }
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
         logger.debug("got a command " + command);
     }
+    //
+    // @Override
+    // public Set<@NonNull String> getSubscribedEventTypes() {
+    // return Collections.singleton(ItemStateEvent.TYPE);
+    // }
+    //
+    // @Override
+    // public @Nullable EventFilter getEventFilter() {
+    // return null;
+    // }
+    //
+    // @Override
+    // public void receive(Event event) {
+    // // publish MQTT message
+    // logger.debug("Received an event: " + event);
+    // ItemStateEvent itemStateEvent = (ItemStateEvent) event;
+    // String topic = outTopic + itemStateEvent.getItemName();
+    // String payload = itemStateEvent.getPayload();
+    // publish(topic, payload);
+    // }
 
-    @Override
-    public Set<@NonNull String> getSubscribedEventTypes() {
-        return Collections.singleton(ItemStateEvent.TYPE);
+    public void added(Item element) {
+        GenericItem genericItem = (GenericItem) element;
+        if (!this.publishGroupFound && genericItem.getName().equals(this.publishGroupName)) {
+            myGroupFound(genericItem);
+            // update status with last known value (to remove the clause about missing group item)
+            statusUpdate(lastStatusUpdatePhase, lastStatusUpdateSuccess, lastStatusUpdateDescription);
+        }
+        List<@NonNull String> groupNames = genericItem.getGroupNames();
+        if (groupNames.contains(this.publishGroupName)) {
+            if (observedItems.add(genericItem)) {
+                genericItem.addStateChangeListener(this);
+            }
+        }
     }
 
-    @Override
-    public @Nullable EventFilter getEventFilter() {
-        return null;
+    public void removed(Item element) {
+        GenericItem genericItem = (GenericItem) element;
+        genericItem.removeStateChangeListener(this);
+        if (this.publishGroupFound && genericItem.getName().equals(this.publishGroupName)) {
+            // someone evil deleted the publish group
+            this.publishGroupFound = false;
+            GroupItem groupItem = (GroupItem) genericItem;
+            for (Item member : groupItem.getAllMembers()) {
+                removed(member);
+            }
+        }
+    }
+
+    public void updated(Item oldElement, Item element) {
+        removed(oldElement);
+        added(element);
     }
 
     @Override
-    public void receive(Event event) {
-        // publish MQTT message
-        logger.debug("Recevied an event: " + event);
-        ItemStateEvent itemStateEvent = (ItemStateEvent) event;
-        String topic = outTopic + itemStateEvent.getItemName();
-        String payload = itemStateEvent.getPayload();
+    public void stateChanged(Item item, State oldState, State newState) {
+        logger.debug("Recevied an event for item: '" + item.getName() + "'");
+        String topic = outTopic + item.getName();
+        String payload = newState.toFullString();
         publish(topic, payload);
     }
 
+    @Override
+    public void stateUpdated(Item item, State state) {
+        // ignored, as no change
+    }
+
 }
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/HandlerPhase.java b/src/main/java/org/openhab/binding/openlicht/handler/HandlerPhase.java
new file mode 100644
index 0000000..acb607d
--- /dev/null
+++ b/src/main/java/org/openhab/binding/openlicht/handler/HandlerPhase.java
@@ -0,0 +1,8 @@
+package org.openhab.binding.openlicht.handler;
+
+public enum HandlerPhase {
+    INIT,
+    CONFIGURATION,
+    COMMUNICATION,
+    FINISH
+}
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/MqttUtils.java b/src/main/java/org/openhab/binding/openlicht/handler/MqttUtils.java
index 5c7328c..dc0dbfa 100644
--- a/src/main/java/org/openhab/binding/openlicht/handler/MqttUtils.java
+++ b/src/main/java/org/openhab/binding/openlicht/handler/MqttUtils.java
@@ -5,18 +5,11 @@ import static org.openhab.binding.openlicht.BindingConstants.*;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.function.BiConsumer;
 
 import org.eclipse.smarthome.core.library.types.DecimalType;
-import org.eclipse.smarthome.core.types.State;
 
 public class MqttUtils {
 
-    @FunctionalInterface
-    public interface UpdateState extends BiConsumer<String, State> {
-
-    }
-
     public static Map<String, DecimalType> handleSimpleJsonMessage(final String message) {
         Map<String, DecimalType> result = new HashMap<>();
         String content = message.substring(1, message.length() - 1);
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/SkyWriterHATHandler.java b/src/main/java/org/openhab/binding/openlicht/handler/SkyWriterHATHandler.java
index 6f9be37..d936713 100644
--- a/src/main/java/org/openhab/binding/openlicht/handler/SkyWriterHATHandler.java
+++ b/src/main/java/org/openhab/binding/openlicht/handler/SkyWriterHATHandler.java
@@ -10,11 +10,14 @@ package org.openhab.binding.openlicht.handler;
 
 import static org.openhab.binding.openlicht.BindingConstants.CHANNEL_FLICK;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.smarthome.core.library.types.StringType;
 import org.eclipse.smarthome.core.thing.ChannelUID;
 import org.eclipse.smarthome.core.thing.Thing;
 import org.eclipse.smarthome.core.types.Command;
 import org.openhab.binding.openlicht.internal.ConfigurationHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The {@link SkyWriterHATHandler} is responsible for handling commands, which are
@@ -24,6 +27,8 @@ import org.openhab.binding.openlicht.internal.ConfigurationHolder;
  */
 public class SkyWriterHATHandler extends AbstractMqttHandler {
 
+    private final @NonNull Logger logger = LoggerFactory.getLogger(SkyWriterHATHandler.class);
+
     public SkyWriterHATHandler(Thing thing, ConfigurationHolder configurationHolder) {
         super(thing, configurationHolder);
     }
diff --git a/src/main/java/org/openhab/binding/openlicht/handler/UpdateState.java b/src/main/java/org/openhab/binding/openlicht/handler/UpdateState.java
new file mode 100644
index 0000000..c03e3cc
--- /dev/null
+++ b/src/main/java/org/openhab/binding/openlicht/handler/UpdateState.java
@@ -0,0 +1,10 @@
+package org.openhab.binding.openlicht.handler;
+
+import java.util.function.BiConsumer;
+
+import org.eclipse.smarthome.core.types.State;
+
+@FunctionalInterface
+public interface UpdateState extends BiConsumer<String, State> {
+
+}
diff --git a/src/main/java/org/openhab/binding/openlicht/internal/ConfigurationHolder.java b/src/main/java/org/openhab/binding/openlicht/internal/ConfigurationHolder.java
index 94a32f7..d5d31dd 100644
--- a/src/main/java/org/openhab/binding/openlicht/internal/ConfigurationHolder.java
+++ b/src/main/java/org/openhab/binding/openlicht/internal/ConfigurationHolder.java
@@ -3,7 +3,7 @@ package org.openhab.binding.openlicht.internal;
 import java.util.concurrent.ScheduledExecutorService;
 
 import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.smarthome.core.events.EventPublisher;
+import org.eclipse.smarthome.core.items.ItemRegistry;
 import org.eclipse.smarthome.core.thing.ThingRegistry;
 import org.eclipse.smarthome.io.transport.mqtt.MqttService;
 import org.osgi.framework.Version;
@@ -20,7 +20,7 @@ public interface ConfigurationHolder {
     ThingRegistry getThingRegistry();
 
     @Nullable
-    EventPublisher getEventPublisher();
+    ItemRegistry getItemRegistry();
 
     Version getVersion();
 
diff --git a/src/main/java/org/openhab/binding/openlicht/internal/DelegateEraserRegistryChangeListener.java b/src/main/java/org/openhab/binding/openlicht/internal/DelegateEraserRegistryChangeListener.java
new file mode 100644
index 0000000..9d0ea78
--- /dev/null
+++ b/src/main/java/org/openhab/binding/openlicht/internal/DelegateEraserRegistryChangeListener.java
@@ -0,0 +1,30 @@
+package org.openhab.binding.openlicht.internal;
+
+import org.eclipse.smarthome.core.common.registry.RegistryChangeListener;
+import org.eclipse.smarthome.core.items.Item;
+import org.openhab.binding.openlicht.handler.EraserHandler;
+
+public class DelegateEraserRegistryChangeListener implements RegistryChangeListener<Item> {
+
+    private EraserHandler handler;
+
+    public DelegateEraserRegistryChangeListener(EraserHandler handler) {
+        this.handler = handler;
+    }
+
+    @Override
+    public void added(Item element) {
+        handler.added(element);
+    }
+
+    @Override
+    public void removed(Item element) {
+        handler.removed(element);
+    }
+
+    @Override
+    public void updated(Item oldElement, Item element) {
+        handler.updated(oldElement, element);
+    }
+
+}
diff --git a/src/main/java/org/openhab/binding/openlicht/internal/OpenLichtHandlerFactory.java b/src/main/java/org/openhab/binding/openlicht/internal/OpenLichtHandlerFactory.java
index 7146bc7..d58fb25 100644
--- a/src/main/java/org/openhab/binding/openlicht/internal/OpenLichtHandlerFactory.java
+++ b/src/main/java/org/openhab/binding/openlicht/internal/OpenLichtHandlerFactory.java
@@ -23,7 +23,7 @@ import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.smarthome.core.events.EventPublisher;
+import org.eclipse.smarthome.core.items.ItemRegistry;
 import org.eclipse.smarthome.core.thing.Thing;
 import org.eclipse.smarthome.core.thing.ThingRegistry;
 import org.eclipse.smarthome.core.thing.ThingTypeUID;
@@ -43,6 +43,7 @@ import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
@@ -55,13 +56,14 @@ import org.slf4j.LoggerFactory;
 @Component(configurationPid = "binding.openlicht", service = ThingHandlerFactory.class)
 public class OpenLichtHandlerFactory extends BaseThingHandlerFactory implements ConfigurationHolder {
 
-    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
-            Stream.of(THING_TYPE_SKYWRITER_HAT, THING_TYPE_POLAR_M600, THING_TYPE_MOTO_360, THING_TYPE_SAMSUNG_S6)
-                    .collect(Collectors.toSet()));
+    private static final Logger logger = LoggerFactory.getLogger(OpenLichtHandlerFactory.class);
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
+            .unmodifiableSet(Stream.of(THING_TYPE_SKYWRITER_HAT, THING_TYPE_POLAR_M600, THING_TYPE_MOTO_360,
+                    THING_TYPE_SAMSUNG_S6, THING_TYPE_ERASER).collect(Collectors.toSet()));
     private @Nullable MqttService service;
     private @Nullable ThingRegistry thingRegistry;
+    private @Nullable ItemRegistry itemRegistry;
     private @Nullable ScheduledExecutorService executor;
-    private @Nullable EventPublisher eventPublisher;
 
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -121,8 +123,8 @@ public class OpenLichtHandlerFactory extends BaseThingHandlerFactory implements
     }
 
     @Override
-    public @Nullable EventPublisher getEventPublisher() {
-        return eventPublisher;
+    public @Nullable ItemRegistry getItemRegistry() {
+        return itemRegistry;
     }
 
     @Override
@@ -132,34 +134,34 @@ public class OpenLichtHandlerFactory extends BaseThingHandlerFactory implements
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC)
     public void setMqttService(MqttService service) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Setting mqtt service to {}", service);
+        logger.info("Setting mqtt service to {}", service);
         this.service = service;
     }
 
     public void unsetMqttService(MqttService service) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Deleting mqtt service {}", service);
+        logger.info("Deleting mqtt service {}", service);
         this.service = null;
     }
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC)
     public void setThingRegistry(ThingRegistry thingRegistry) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Setting thing registry to {}", thingRegistry);
+        logger.info("Setting thing registry to {}", thingRegistry);
         this.thingRegistry = thingRegistry;
     }
 
-    public void unsetThingRegistry(ThingRegistry service) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Deleting thing registry {}", service);
+    public void unsetThingRegistry(ThingRegistry thingRegistry) {
+        logger.info("Deleting thing registry {}", thingRegistry);
         this.thingRegistry = null;
     }
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC)
-    public void setEventPublisher(EventPublisher eventPublisher) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Setting event publisher to {}", thingRegistry);
-        this.eventPublisher = eventPublisher;
+    public void setItemRegistry(ItemRegistry itemRegistry) {
+        logger.info("Setting item registry to {}", itemRegistry);
+        this.itemRegistry = itemRegistry;
     }
 
-    public void unsetEventPublisher(EventPublisher eventPublisher) {
-        LoggerFactory.getLogger(OpenLichtHandlerFactory.class).info("Deleting event publisher {}", service);
-        this.eventPublisher = null;
+    public void unsetItemRegistry(ItemRegistry itemRegistry) {
+        logger.info("Deleting item registry {}", itemRegistry);
+        this.itemRegistry = null;
     }
 }
-- 
GitLab