package org.myrobotlab.service; import java.sql.Timestamp; import java.util.Arrays; import org.eclipse.paho.client.mqttv3.IMqttActionListener; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.IMqttToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; //import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.slf4j.Logger; /** * * Mqtt - Mqtt is a machine-to-machine (M2M)/"Internet of Things" connectivity * protocol. It was designed as an extremely lightweight publish/subscribe * messaging transport. It is useful for connections with remote locations where * a small code footprint is required and/or network bandwidth is at a premium. * http://mqtt.org/ * * Much of this code was copied from the sample program provided by the Paho * project * http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.java.git/tree/org.eclipse * .paho.sample.mqttv3app/src/main/java/org/eclipse/paho/sample/mqttv3app/ * SampleAsyncCallBack.java * * @author kmcgerald * */ public class Mqtt extends Service implements MqttCallback { /** * Disconnect in a non-blocking way and then sit back and wait to be notified * that the action has completed. */ public class Disconnector { public void doDisconnect() { // Disconnect the client log.info("Disconnecting"); IMqttActionListener discListener = new IMqttActionListener() { public void carryOn() { synchronized (waiter) { donext = true; waiter.notifyAll(); } } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { ex = exception; state = ERROR; log.info("Disconnect failed" + exception); carryOn(); } @Override public void onSuccess(IMqttToken asyncActionToken) { log.info("Disconnect Completed"); state = DISCONNECTED; carryOn(); } }; try { client.disconnect("Disconnect sample context", discListener); } catch (MqttException e) { state = ERROR; donext = true; ex = e; } } } /** * Connect in a non-blocking way and then sit back and wait to be notified * that the action has completed. */ public class MqttConnector { public MqttConnector() { } public void doConnect() { // Connect to the server // Get a token and setup an asynchronous listener on the token which // will be notified once the connect completes info("Connecting to %s with client Id %s", brokerURL, client.getClientId()); IMqttActionListener conListener = new IMqttActionListener() { public void carryOn() { synchronized (waiter) { donext = true; waiter.notifyAll(); } } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { ex = exception; state = ERROR; log.info("connect failed" + exception); carryOn(); } @Override public void onSuccess(IMqttToken asyncActionToken) { log.info("Connected"); state = CONNECTED; carryOn(); } }; try { // Connect using a non-blocking connect client.connect(conOpt, "Connect sample context", conListener); } catch (MqttException e) { // If though it is a non-blocking connect an exception can be // thrown if validation of parms fails or other checks such // as already connected fail. state = ERROR; donext = true; ex = e; } } } /** * Publish in a non-blocking way and then sit back and wait to be notified * that the action has completed. */ public class Publisher { public void doPublish(String topicName, int qos, byte[] payload) { // Send / publish a message to the server // Get a token and setup an asynchronous listener on the token which // will be notified once the message has been delivered MqttMessage message = new MqttMessage(payload); message.setQos(qos); String time = new Timestamp(System.currentTimeMillis()).toString(); log.info("Publishing at: " + time + " to topic \"" + topicName + "\" qos " + qos); // Setup a listener object to be notified when the publish // completes. // IMqttActionListener pubListener = new IMqttActionListener() { public void carryOn() { synchronized (waiter) { donext = true; waiter.notifyAll(); } } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { ex = exception; state = ERROR; log.info("Publish failed" + exception); carryOn(); } @Override public void onSuccess(IMqttToken asyncActionToken) { log.info("Publish Completed"); state = PUBLISHED; carryOn(); } }; try { // Publish the message client.publish(topicName, message, "Pub sample context", pubListener); } catch (MqttException e) { state = ERROR; donext = true; ex = e; } } } /** * Subscribe in a non-blocking way and then sit back and wait to be notified * that the action has completed. */ public class Subscriber { public void doSubscribe(String topicName, int qos) { // Make a subscription // Get a token and setup an asynchronous listener on the token which // will be notified once the subscription is in place. log.info("Subscribing to topic \"" + topicName + "\" qos " + qos); IMqttActionListener subListener = new IMqttActionListener() { public void carryOn() { synchronized (waiter) { donext = true; waiter.notifyAll(); } } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { ex = exception; state = ERROR; log.info("Subscribe failed" + exception); carryOn(); } @Override public void onSuccess(IMqttToken asyncActionToken) { log.info("Subscribe Completed"); state = SUBSCRIBED; carryOn(); } }; try { client.subscribe(topicName, qos, "Subscribe sample context", subListener); } catch (MqttException e) { state = ERROR; donext = true; ex = e; } } } int state = BEGIN; static final int BEGIN = 0; static final int CONNECTED = 1; static final int PUBLISHED = 2; static final int SUBSCRIBED = 3; static final int DISCONNECTED = 4; static final int FINISH = 5; static final int ERROR = 6; static final int DISCONNECT = 7; private static final long serialVersionUID = 1L; transient MqttAsyncClient client; boolean quietMode = false; String action = "publish"; String message = "Message from async callback client"; int qos = 2; String brokerURL = "m2m.eclipse.org"; int port = 1883; String clientId = "MRL Mqtt client"; String subTopic = "mrl/#"; String pubTopic = "mrl"; boolean cleanSession = true; // Non durable subscriptions boolean ssl = false; String password = null; String userName = null; Throwable ex = null; Object waiter = new Object(); boolean donext = false; transient MqttConnectOptions conOpt; String[] tokens; public final static Logger log = LoggerFactory.getLogger(Mqtt.class); public static void main(String[] args) { try { LoggingFactory.init(Level.INFO); Python python = new Python("python"); python.startService(); Runtime.start("mqtt", "Mqtt"); Runtime.start("gui", "GUIService"); } catch (Exception e) { Logging.logError(e); } } public Mqtt(String n) { super(n); } /** * @see MqttCallback#connectionLost(Throwable) */ @Override public void connectionLost(Throwable cause) { // Called when the connection to the server has been lost. // An application may choose to implement reconnection // logic at this point. This sample simply exits. error("Connection to " + brokerURL + " lost!" + cause); } /** * @see MqttCallback#deliveryComplete(IMqttDeliveryToken) */ @Override public void deliveryComplete(IMqttDeliveryToken token) { // Called when a message has been delivered to the // server. The token passed in here is the same one // that was returned from the original call to publish. // This allows applications to perform asynchronous // delivery without blocking until delivery completes. // // This sample demonstrates asynchronous deliver, registering // a callback to be notified on each call to publish. // // The deliveryComplete method will also be called if // the callback is set on the client // // note that token.getTopics() returns an array so we convert to a // string // before printing it on the console log.info("Delivery complete callback: Publish Completed " + Arrays.toString(token.getTopics())); } /** * @see MqttCallback#messageArrived(String, MqttMessage) */ @Override public void messageArrived(String topic, MqttMessage message) throws MqttException { // Called when a message arrives from the server that matches any // subscription made by the client String time = new Timestamp(System.currentTimeMillis()).toString(); String messageStr = "Time: " + time + "\tTopic: " + topic + "\tMessage: " + new String(message.getPayload()) + "\tQoS: " + message.getQos(); log.info(messageStr); tokens = messageStr.split("\t"); invoke("publishMqttMessage"); } public void publish(String content) throws Throwable { // MqttPersistenceException, // MqttException { // Use a state machine to decide which step to do next. State change // occurs // when a notification is received that an Mqtt action has completed while (state != FINISH) { switch (state) { case BEGIN: // Connect using a non-blocking connect MqttConnector con = new MqttConnector(); con.doConnect(); break; case CONNECTED: // Publish using a non-blocking publisher Publisher pub = new Publisher(); pub.doPublish(pubTopic, qos, content.getBytes()); break; case PUBLISHED: state = DISCONNECT; donext = true; break; case DISCONNECT: Disconnector disc = new Disconnector(); disc.doDisconnect(); break; case ERROR: throw ex; case DISCONNECTED: state = FINISH; donext = true; break; } // if (state != FINISH) { // Wait until notified about a state change and then perform next // action waitForStateChange(10000); // } } } /****************************************************************/ /* End of MqttCallback methods */ /****************************************************************/ public String[] publishMqttMessage() { // tokens = message.split(","); return tokens; } public void setBroker(String broker) { brokerURL = broker; } /****************************************************************/ /* Methods to implement the MqttCallback interface */ /****************************************************************/ public void setClientId(String cId) { clientId = cId; } public void setPubTopic(String topic) { pubTopic = topic; } public void setQos(int q) { qos = q; } public void setSubTopic(String topic) { subTopic = topic; } public void startClient() throws MqttException { MemoryPersistence persistence = new MemoryPersistence(); try { conOpt = new MqttConnectOptions(); conOpt.setCleanSession(true); // Construct the MqttClient instance client = new MqttAsyncClient(this.brokerURL, clientId, persistence); // Set this wrapper as the callback handler client.setCallback(this); } catch (MqttException e) { log.info("Unable to set up client: " + e.toString()); } } /** * Subscribe to a topic on an Mqtt server Once subscribed this method waits * for the messages to arrive from the server that match the subscription. It * continues listening for messages until the enter key is pressed. * * @param topicName * to subscribe to (can be wild carded) * @param qos * the maximum quality of service to receive messages at for this * subscription * @throws MqttException */ public void subscribe(String topicName, int qos) throws Throwable { // Use a state machine to decide which step to do next. State change // occurs // when a notification is received that an Mqtt action has completed while (state != FINISH) { switch (state) { case BEGIN: // Connect using a non-blocking connect MqttConnector con = new MqttConnector(); con.doConnect(); break; case CONNECTED: // Subscribe using a non-blocking subscribe Subscriber sub = new Subscriber(); sub.doSubscribe(topicName, qos); break; case SUBSCRIBED: // We're not going to do anything extra in this state so the // service can keep running // state = DISCONNECT; donext = true; break; case DISCONNECT: Disconnector disc = new Disconnector(); disc.doDisconnect(); break; case ERROR: throw ex; case DISCONNECTED: state = FINISH; donext = true; break; } // if (state != FINISH && state != DISCONNECT) { waitForStateChange(10000); // } } } /** * Wait for a maximum amount of time for a state change event to occur * * @param maxTTW * maximum time to wait in milliseconds * @throws MqttException */ private void waitForStateChange(int maxTTW) throws MqttException { synchronized (waiter) { if (!donext) { try { waiter.wait(maxTTW); } catch (InterruptedException e) { log.info("timed out"); e.printStackTrace(); } if (ex != null) { throw (MqttException) ex; } } donext = false; } } /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(Mqtt.class.getCanonicalName()); meta.addDescription( "This is an Mqtt client based on the Paho Mqtt client library. Mqtt is a machine-to-machine (M2M)/'Internet of Things' connectivity protocol. See http://mqtt.org"); meta.addCategory("data", "cloud"); meta.addDependency("org.eclipse.paho", "1.0"); return meta; } }