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