# HG changeset patch # User William Welliver # Date 1582412580 18000 # Sat Feb 22 18:03:00 2020 -0500 # Node ID 397394898026b7b5c956ff90834ec068c7762f3d # Parent c01cdd7e86dfb4fc02a819d073c252a533b2308f rewrite for openhab 2.5 diff --git a/pom.xml b/pom.xml --- a/pom.xml +++ b/pom.xml @@ -1,166 +1,15 @@ - - - - 4.0.0 - - - org.eclipse.smarthome.config - org.eclipse.smarthome.config.discovery.mdns - 0.9.0-SNAPSHOT - - - org.eclipse.paho - org.eclipse.paho.client.mqttv3 - 1.0.2 - - - - - org.openhab - pom-tycho - 2.4.0-SNAPSHOT - - - - - false - 2.4.0-SNAPSHOT - - - org.openhab.binding.lutronmqtt - - LutronMQTT Binding - eclipse-plugin + - - src/test - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.19.1 - - - test - test - - - **/*Test.java - - - - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.7 - 1.7 - - - - compiletests - test-compile - - testCompile - - - - - - + 4.0.0 - - - jcenter - JCenter Repository - https://jcenter.bintray.com/ - - true - never - - - false - - - - artifactory - JFrog Artifactory Repository - https://openhab.jfrog.io/openhab/libs-release - - true - never - - - false - - - - - - - - - jcenter - JCenter Repository - https://jcenter.bintray.com/ - - true - never - - - false - - + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.3-SNAPSHOT + - - openhab-artifactory-release - JFrog Artifactory Repository - https://openhab.jfrog.io/openhab/libs-release - - true - never - - - false - - + org.openhab.binding.lutron-mqtt - - - openhab-artifactory-snapshot - JFrog Artifactory Repository - https://openhab.jfrog.io/openhab/libs-snapshot - - false - - - true - always - - - - - - p2-smarthome - https://openhab.jfrog.io/openhab/eclipse-smarthome-stable - p2 - - - - - p2-openhab-deps-repo - https://dl.bintray.com/openhab/p2/openhab-deps-repo/${ohdr.version} - p2 - - - + openHAB Add-ons :: Bundles :: Lutron-MQTT Binding diff --git a/src/main/history/dependencies.xml b/src/main/history/dependencies.xml new file mode 100644 --- /dev/null +++ b/src/main/history/dependencies.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/java/org/openhab/binding/lutronmqtt/LutronMQTTBindingConstants.java b/src/main/java/org/openhab/binding/lutronmqtt/LutronMQTTBindingConstants.java --- a/src/main/java/org/openhab/binding/lutronmqtt/LutronMQTTBindingConstants.java +++ b/src/main/java/org/openhab/binding/lutronmqtt/LutronMQTTBindingConstants.java @@ -12,11 +12,13 @@ */ package org.openhab.binding.lutronmqtt; -import com.google.common.collect.ImmutableSet; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.thing.ThingTypeUID; +import java.util.Collections; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The {@link LutronMQTTBindingConstants} class defines common constants, which are @@ -34,10 +36,12 @@ public final static ThingTypeUID THING_TYPE_VARIABLE_FAN = new ThingTypeUID(BINDING_ID, "variableFan"); public final static ThingTypeUID THING_TYPE_REMOTE = new ThingTypeUID(BINDING_ID, "remote"); - public final static Set SUPPORTED_HUBS_UIDS = ImmutableSet.of(THING_TYPE_MQTTHUB); + public final static Set SUPPORTED_HUBS_UIDS = Collections.unmodifiableSet( + Stream.of(THING_TYPE_MQTTHUB).collect(Collectors.toSet())); - public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_DIMMABLE_LIGHT, - THING_TYPE_VARIABLE_FAN, THING_TYPE_REMOTE); + public final static Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet( + Stream.of(THING_TYPE_DIMMABLE_LIGHT, + THING_TYPE_VARIABLE_FAN, THING_TYPE_REMOTE).collect(Collectors.toSet())); // List of all Channel ids public final static String PROPERTY_UUID = "uuid"; diff --git a/src/main/java/org/openhab/binding/lutronmqtt/discovery/LutronMQTTHubDiscoveryParticipant.java b/src/main/java/org/openhab/binding/lutronmqtt/discovery/LutronMQTTHubDiscoveryParticipant.java --- a/src/main/java/org/openhab/binding/lutronmqtt/discovery/LutronMQTTHubDiscoveryParticipant.java +++ b/src/main/java/org/openhab/binding/lutronmqtt/discovery/LutronMQTTHubDiscoveryParticipant.java @@ -44,9 +44,13 @@ logger.warn("name: " + service.getName()); logger.warn("qname: " + service.getQualifiedName()); logger.warn("nice: " + service.getNiceTextString()); + logger.warn("text: " + new String(service.getTextBytes())); + Enumeration pn = service.getPropertyNames(); - while(pn.hasMoreElements()) - logger.warn("prop: " + pn.nextElement()); + while(pn.hasMoreElements()) { + String name; + logger.warn("prop: " + (name = pn.nextElement()) + " " + service.getPropertyString(name)); + } logger.debug("Got discovered device."); if (true /*getDiscoveryServiceCallback() != null*/) { @@ -69,6 +73,11 @@ String u = service.getPropertyString("uuid"); String s = service.getServer(); + if(s== null || s.isEmpty()) { + String [] addrs = service.getHostAddresses(); + if(addrs.length > 0) s = addrs[0]; + } + if(u == null || s == null || u.isEmpty() || s.isEmpty()) // incomplete discovery result { logger.warn("Found lutron-mqtt service but missing data. Try restarting device."); diff --git a/src/main/java/org/openhab/binding/lutronmqtt/handler/LutronMQTTHubHandler.java b/src/main/java/org/openhab/binding/lutronmqtt/handler/LutronMQTTHubHandler.java --- a/src/main/java/org/openhab/binding/lutronmqtt/handler/LutronMQTTHubHandler.java +++ b/src/main/java/org/openhab/binding/lutronmqtt/handler/LutronMQTTHubHandler.java @@ -12,22 +12,25 @@ */ package org.openhab.binding.lutronmqtt.handler; -import com.google.common.collect.ImmutableList; import com.google.gson.Gson; +import jersey.repackaged.com.google.common.collect.ImmutableList; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.paho.client.mqttv3.*; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.thing.*; import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler; import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.io.transport.mqtt.*; +import org.eclipse.smarthome.io.transport.mqtt.reconnect.PeriodicReconnectStrategy; import org.openhab.binding.lutronmqtt.internal.LutronMQTTConfiguration; import org.openhab.binding.lutronmqtt.model.LutronDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.*; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -41,8 +44,7 @@ * * @author William Welliver - Initial contribution */ -public class LutronMQTTHubHandler extends BaseBridgeHandler implements MqttCallback { - +public class LutronMQTTHubHandler extends BaseBridgeHandler implements MqttMessageSubscriber, MqttConnectionObserver { private final Logger logger = LoggerFactory.getLogger(LutronMQTTHubHandler.class); private Collection deviceStatusListeners = new HashSet<>(); @@ -52,15 +54,15 @@ @Nullable private LutronMQTTConfiguration config; private String token; - private MqttClient mqttClient; + private MqttBrokerConnection mqttClient; private Gson gson = new Gson(); private Map devicesByLinkAddress = new HashMap<>(); - private ScheduledFuture reconnectJob; private ScheduledFuture onlineTimeout; private ScheduledFuture allItemsJob; public LutronMQTTHubHandler(Bridge thing) { super(thing); + logger.warn("LutronMQTTHubHandler create."); } @Override @@ -70,72 +72,86 @@ Map properties = getThing().getProperties(); token = (String) config.get(CONFIG_TOKEN); - final String uuid = properties.get(PROPERTY_UUID); final String broker = properties.get(PROPERTY_URL); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE); - MemoryPersistence persistence = new MemoryPersistence(); String clientId = "openhab-lutron-mqtt-" + (System.currentTimeMillis()/1000); - try { - if(mqttClient == null || !mqttClient.isConnected()) { - logger.info("Attempting to connect to MQTT Broker."); - MqttClient client = new MqttClient(broker, clientId, persistence); - MqttConnectOptions connOpts = new MqttConnectOptions(); -// connOpts.setCleanSession(true); -// connOpts.setKeepAliveInterval(30); -// connOpts.setConnectionTimeout(90); - client.connect(connOpts); - mqttClient = client; - setupSubscriptions(); + if(mqttClient == null || mqttClient.connectionState() == MqttConnectionState.DISCONNECTED) { + logger.warn("Attempting to connect to MQTT Broker."); + URI brokerUri = null; + try { + brokerUri = new URI(broker); + } catch (URISyntaxException e) { + logger.error("Lutron-MQTT broker url was invalid: " + broker); + goOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Lutron-MQTT broker url was invalid: " + broker); + return; + } + + MqttBrokerConnection brokerConnection = new MqttBrokerConnection(brokerUri.getHost(), brokerUri.getPort(), false, clientId); + brokerConnection.addConnectionObserver(this); + brokerConnection.setReconnectStrategy(new PeriodicReconnectStrategy()); + updateStatus(ThingStatus.OFFLINE, "Preparing to connect to " + brokerUri.toASCIIString()); + + Boolean bool = null; + try { + mqttClient = brokerConnection; + bool = brokerConnection.start().get(); + } catch (InterruptedException|ExecutionException e) { + logger.warn("MQTT startup failed.", e); + } + logger.warn("MQTT startup returned " + bool); } - } catch (MqttException e) { - logger.error("Unable to connect to MQTT broker at " + broker, e); - goOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Unable to connect to MQTT broker at broker"); - } + + // logger.error("Unable to connect to MQTT broker at " + broker, e); + // goOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Unable to connect to MQTT broker at broker"); + } - + protected void updateStatus(ThingStatus status, String statusDetail) { + this.updateStatus(status, ThingStatusDetail.NONE, (String)statusDetail); + } @Override public void dispose() { logger.info("Disposing of handler " + this); super.dispose(); - try { - MqttClient cl = mqttClient; - cl.setCallback(null); - cl.unsubscribe(new String[] {"lutron/status", "lutron/events", "lutron/remote"}); - cl.disconnectForcibly(3000); - cl.close(); - mqttClient = null; + + mqttClient.stop(); cancelJobs(); - } catch (MqttException e) { - logger.warn("Error while disconnecting", e); - } + mqttClient = null; } private void cancelJobs() { - if(reconnectJob != null && !reconnectJob.isCancelled()) - reconnectJob.cancel(true); if(allItemsJob != null && !allItemsJob.isCancelled()) allItemsJob.cancel(true); - if(onlineTimeout != null && !allItemsJob.isCancelled()) + if(onlineTimeout != null && !onlineTimeout.isCancelled()) onlineTimeout.cancel(true); } private void setupSubscriptions() { - mqttClient.setCallback(this); try { - mqttClient.subscribe(new String[] {"lutron/status", "lutron/events", "lutron/remote"}); - } catch (MqttException e) { - logger.error("subscribe failed.", e); - goOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Unable to subscribe to events"); + mqttClient.unsubscribeAll().get(); + } catch (InterruptedException|ExecutionException e) { + logger.warn("An error occurred while unsubscribing.", e); } - + logger.info("Subscribing."); + for(String topic : new String[] {"lutron/status", "lutron/events", "lutron/remote"}) { + try { + logger.info("subscribing to " + topic); + if(!mqttClient.subscribe(topic, this).get()) { + logger.error("subscribe failed."); + goOffline(ThingStatusDetail.COMMUNICATION_ERROR, "Unable to subscribe to events"); + } + } catch (InterruptedException|ExecutionException e) { + logger.warn("An error occurred while subscribing.", e); + } + } + logger.info("Subscribed."); cancelJobs(); + logger.info("Scheduling item request."); allItemsJob = scheduler.schedule(new Runnable() { @Override public void run() { @@ -145,13 +161,18 @@ } private void requestAllItems() { + logger.warn("Requesting all items"); + byte[] b = new byte[0]; try { - byte[] b = ("{\"cmd\": \"GetDevices\", \"args\": {}}").getBytes("UTF-8"); - mqttClient.publish("lutron/commands", (b), 0, false); - } catch (MqttException e) { - logger.warn("Error Sending message.", e); + b = ("{\"cmd\": \"GetDevices\", \"args\": {}}").getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { - logger.warn("Error encoding message.", e); + logger.warn("Unable to encode JSON.", e); + } + try { + Boolean res = mqttClient.publish("lutron/commands", (b), 0, false).get(); + logger.warn("Publish returned " + res); + } catch(Exception e) { + logger.warn("Exception while publishing.", e); } allItemsJob = scheduler.schedule(new Runnable() { @@ -190,44 +211,8 @@ } @Override - public void connectionLost(Throwable throwable) { - goOffline(ThingStatusDetail.BRIDGE_OFFLINE, throwable.getMessage()); - logger.warn("Lost connection to MQTT server. Will rety in 20 seconds.", throwable); - if(mqttClient != null) scheduleReconnect(20); - } - - private void scheduleReconnect(int seconds) { - if(reconnectJob == null || reconnectJob.isDone()) { - reconnectJob = scheduler.schedule(new Runnable() { - @Override - public void run() { - reconnect(); - } - }, seconds, TimeUnit.SECONDS); - } - } - - private void reconnect() { - if(mqttClient == null) { - logger.info("Skipping reconnect because MQTT client is null."); - return; - } - logger.info("Attempting to reconnect to MQTT server"); - initialize(); - ThingStatusInfo info = getThing().getStatusInfo(); - if(info.getStatus() != ThingStatus.ONLINE && - (info.getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE || - info.getStatusDetail() == ThingStatusDetail.COMMUNICATION_ERROR - )) // we're not online but have a retryable status - scheduleReconnect(20); - else { - reconnectJob = null; - } - } - - @Override - public void messageArrived(String s, MqttMessage mqttMessage) throws Exception { - logger.debug("messageArrived: " + s + " " + mqttMessage.toString()); + public void processMessage(String s, byte[] mqttMessage) { + logger.warn("messageArrived: " + s + " " + mqttMessage.toString()); switch(s) { case "lutron/status": handleStatusMessage(mqttMessage); @@ -241,9 +226,9 @@ } } - private void handleGatewayEvent(MqttMessage mqttMessage) { + private void handleGatewayEvent(byte[] mqttMessage) { //logger.info("gateway event: " + new String(mqttMessage.getPayload())); - Map msg = gson.fromJson(new String(mqttMessage.getPayload()), HashMap.class); + Map msg = gson.fromJson(new String(mqttMessage), HashMap.class); if(msg.containsKey("cmd")) { String key = (String)msg.get("cmd"); @@ -297,13 +282,8 @@ } protected void requestUpdateForDevice(int linkAddress) { - try { byte[] b = ("{\"cmd\":\"RuntimePropertyQuery\", \"args\":{\"Params\":[[" + linkAddress + ",15,[1]]]}}").getBytes(); - MqttMessage message = new MqttMessage(b); - mqttClient.publish("lutron/commands", message); - } catch (MqttException e) { - logger.error("Error publishing message", e); - } + mqttClient.publish("lutron/commands", b); } protected void informDeviceListeners(LutronDevice device) { @@ -318,17 +298,16 @@ return devicesByLinkAddress.get(linkAddress); } - private void handleRemoteEvent(MqttMessage mqttMessage) { - logger.info("remote event: " + new String(mqttMessage.getPayload())); + private void handleRemoteEvent(byte[] mqttMessage) { + logger.info("remote event: " + new String(mqttMessage)); - Map msg = gson.fromJson(new String(mqttMessage.getPayload()), HashMap.class); + Map msg = gson.fromJson(new String(mqttMessage), HashMap.class); // {"serial" : "C726CA", "action": "down", "button": "select"} - } - private void handleStatusMessage(MqttMessage mqttMessage) { - logger.info("status message: " + new String(mqttMessage.getPayload())); - Map msg = gson.fromJson(new String(mqttMessage.getPayload()), HashMap.class); + private void handleStatusMessage(byte[] mqttMessage) { + logger.info("status message: " + new String(mqttMessage)); + Map msg = gson.fromJson(new String(mqttMessage), HashMap.class); if(msg.containsKey("state") && "running".equals(msg.get("state"))) { if(onlineTimeout != null) onlineTimeout.cancel(true); @@ -344,21 +323,54 @@ protected void onlineTimeoutOccurred() { goOffline(ThingStatusDetail.BRIDGE_OFFLINE, "Too long between status announcements."); - } - - @Override - public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { - // We really don't care too much about this status. + try { + mqttClient.stop().get(); + Thread.sleep(5000); + initialize(); + } catch(Exception e) { + logger.warn("An error occurred while restarting the service.", e); + } } public void setDesiredState(int deviceId, Map lightState) { + byte[] bytes = new byte[0]; try { - byte[] bytes = (gson.toJson(lightState)).getBytes("UTF-8"); - // MqttMessage message = new MqttMessage(bytes); - mqttClient.publish("lutron/commands", bytes, 0, false); - logger.info("Sent message."); - } catch (MqttException|UnsupportedEncodingException e) { - e.printStackTrace(); + bytes = (gson.toJson(lightState)).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.warn("Error encoding JSON.", e); + } + // MqttMessage message = new MqttMessage(bytes); + Boolean res = null; + try { + res = mqttClient.publish("lutron/commands", bytes, 0, false).get(); + if(res) + logger.info("Sent message."); + else logger.warn("Failed to send message: " + new String(bytes)); + } catch (InterruptedException|ExecutionException e) { + logger.warn("Error sending message.", e); + } + } + + @Override + public void connectionStateChanged(MqttConnectionState mqttConnectionState, @Nullable Throwable throwable) { + if(mqttConnectionState == MqttConnectionState.CONNECTED) { + logger.info("MQTT connection state changed to CONNECTED."); + goOnline(); + logger.info("Online"); + scheduler.schedule(new Runnable() { + @Override + public void run() { + setupSubscriptions(); + logger.info("Subscribed."); + + } + },1, TimeUnit.SECONDS); + } else if (mqttConnectionState == MqttConnectionState.CONNECTING) { + goOffline(ThingStatusDetail.BRIDGE_OFFLINE, "MQTT Reconnecting"); + } else if (mqttConnectionState == MqttConnectionState.DISCONNECTED) { + goOffline(ThingStatusDetail.BRIDGE_OFFLINE, "MQTT Disconnected"); + logger.warn("Lost connection to MQTT server."); + cancelJobs(); } } } diff --git a/src/main/java/org/openhab/binding/lutronmqtt/internal/LutronMQTTHandlerFactory.java b/src/main/java/org/openhab/binding/lutronmqtt/internal/LutronMQTTHandlerFactory.java --- a/src/main/java/org/openhab/binding/lutronmqtt/internal/LutronMQTTHandlerFactory.java +++ b/src/main/java/org/openhab/binding/lutronmqtt/internal/LutronMQTTHandlerFactory.java @@ -17,6 +17,7 @@ import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.config.discovery.DiscoveryService; import org.eclipse.smarthome.core.thing.Bridge; import org.eclipse.smarthome.core.thing.ThingUID; @@ -33,6 +34,8 @@ import org.openhab.binding.lutronmqtt.handler.LutronMQTTVariableFanHandler; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link LutronMQTTHandlerFactory} is responsible for creating things and thing @@ -44,6 +47,8 @@ @NonNullByDefault public class LutronMQTTHandlerFactory extends BaseThingHandlerFactory { + private final static Logger logger = LoggerFactory.getLogger(LutronMQTTHandlerFactory.class); + public final static Set SUPPORTED_THING_TYPES_UIDS = LutronMQTTBindingConstants.SUPPORTED_THING_TYPES_UIDS; private Map> discoveryServiceRegs = new HashMap<>(); @@ -53,11 +58,15 @@ } @Override + @Nullable protected ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + logger.warn("Creating a handler for " + thing.getThingTypeUID() + ", " + thing.getLabel()); + if (thingTypeUID.equals(THING_TYPE_MQTTHUB)) { + logger.warn("Creating hub handler"); LutronMQTTHubHandler handler = new LutronMQTTHubHandler((Bridge) thing); registerDeviceDiscoveryService(handler); return handler; diff --git a/ESH-INF/binding/binding.xml b/src/main/resources/ESH-INF/binding/binding.xml rename from ESH-INF/binding/binding.xml rename to src/main/resources/ESH-INF/binding/binding.xml diff --git a/ESH-INF/i18n/lutronmqtt_xx_XX.properties b/src/main/resources/ESH-INF/i18n/lutronmqtt_xx_XX.properties rename from ESH-INF/i18n/lutronmqtt_xx_XX.properties rename to src/main/resources/ESH-INF/i18n/lutronmqtt_xx_XX.properties diff --git a/ESH-INF/thing/hub.xml b/src/main/resources/ESH-INF/thing/hub.xml rename from ESH-INF/thing/hub.xml rename to src/main/resources/ESH-INF/thing/hub.xml diff --git a/ESH-INF/thing/thing-types.xml b/src/main/resources/ESH-INF/thing/thing-types.xml rename from ESH-INF/thing/thing-types.xml rename to src/main/resources/ESH-INF/thing/thing-types.xml