package com.adafruit.bluefruit.le.connect.mqtt; import android.content.Context; import android.util.Log; import android.widget.Toast; import com.adafruit.bluefruit.le.connect.R; import org.eclipse.paho.android.service.MqttAndroidClient; import org.eclipse.paho.android.service.MqttTraceHandler; 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.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import java.util.UUID; public class MqttManager implements IMqttActionListener, MqttCallback, MqttTraceHandler { // Log private final static String TAG = MqttManager.class.getSimpleName(); // Singleton private static MqttManager mInstance = null; // Types public enum MqqtConnectionStatus { CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, ERROR, NONE } public static int MqqtQos_AtMostOnce = 0; public static int MqqtQos_AtLeastOnce = 1; public static int MqqtQos_ExactlyOnce = 2; // Data private MqttAndroidClient mMqttClient; private MqttManagerListener mListener; private MqqtConnectionStatus mMqqtClientStatus = MqqtConnectionStatus.NONE; private Context mContext; public static MqttManager getInstance(Context context) { if (mInstance == null) { mInstance = new MqttManager(context); } return mInstance; } public MqttManager(Context context) { mContext = context.getApplicationContext(); } @Override public void finalize() throws Throwable { try { if (mMqttClient != null) { mMqttClient.unregisterResources(); } } finally { super.finalize(); } } public MqqtConnectionStatus getClientStatus() { return mMqqtClientStatus; } public void setListener(MqttManagerListener listener) { mListener = listener; } // region MQTT public void subscribe(String topic, int qos) { if (mMqttClient != null && mMqqtClientStatus == MqqtConnectionStatus.CONNECTED && topic != null) { try { Log.d(TAG, "Mqtt: subscribe to " + topic + " qos:" + qos); mMqttClient.subscribe(topic, qos); } catch (MqttException e) { Log.e(TAG, "Mqtt:x subscribe error: ", e); } } } public void unsubscribe(String topic) { if (mMqttClient != null && mMqqtClientStatus == MqqtConnectionStatus.CONNECTED && topic != null) { try { Log.d(TAG, "Mqtt: unsubscribe from " + topic); mMqttClient.unsubscribe(topic); } catch (MqttException e) { Log.e(TAG, "Mqtt:x unsubscribe error: ", e); } } } public void publish(String topic, String payload, int qos) { if (mMqttClient != null && mMqqtClientStatus == MqqtConnectionStatus.CONNECTED && topic != null) { boolean retained = false; try { Log.d(TAG, "Mqtt: publish " + payload + " for topic " + topic + " qos:" + qos); mMqttClient.publish(topic, payload.getBytes(), qos, retained, null, null); } catch (MqttException e) { Log.e(TAG, "Mqtt:x publish error: ", e); } } } public void disconnect() { if (mMqttClient != null && mMqqtClientStatus == MqqtConnectionStatus.CONNECTED) { try { Log.d(TAG, "Mqtt: disconnect"); // mMqqtClientStatus = MqqtConnectionStatus.DISCONNECTING; mMqqtClientStatus = MqqtConnectionStatus.DISCONNECTED; // Note: it seems that the disconnected callback is never invoked. So we fake here that the final state is disconnected mMqttClient.disconnect(null, this); mMqttClient.unregisterResources(); mMqttClient = null; } catch (MqttException e) { Log.e(TAG, "Mqtt:x disconnection error: ", e); } } } public void connectFromSavedSettings(Context context) { MqttSettings settings = MqttSettings.getInstance(context); String host = settings.getServerAddress(); int port = settings.getServerPort(); String username = settings.getUsername(); String password = settings.getPassword(); boolean cleanSession = settings.isCleanSession(); boolean sslConnection = settings.isSslConnection(); connect(context, host, port, username, password, cleanSession, sslConnection); } public void connect(Context context, String host, int port, String username, String password, boolean cleanSession, boolean sslConnection) { String clientId = "Bluefruit_"+ UUID.randomUUID().toString(); final int timeout = MqttConnectOptions.CONNECTION_TIMEOUT_DEFAULT; final int keepalive = MqttConnectOptions.KEEP_ALIVE_INTERVAL_DEFAULT; String message = null; String topic = null; int qos = 0; boolean retained = false; String uri; if (sslConnection) { uri = "ssl://" + host + ":" + port; } else { uri = "tcp://" + host + ":" + port; } Log.d(TAG, "Mqtt: Create client: "+clientId); mMqttClient = new MqttAndroidClient(context, uri, clientId); mMqttClient.registerResources(mContext); MqttConnectOptions conOpt = new MqttConnectOptions(); Log.d(TAG, "Mqtt: clean session:" +(cleanSession?"yes":"no")); conOpt.setCleanSession(cleanSession); conOpt.setConnectionTimeout(timeout); conOpt.setKeepAliveInterval(keepalive); if (username != null && username.length() > 0) { Log.d(TAG, "Mqtt: username: " + username); conOpt.setUserName(username); } if (password != null && password.length() > 0) { Log.d(TAG, "Mqtt: password: " + password); conOpt.setPassword(password.toCharArray()); } boolean doConnect = true; if ((message != null && message.length() > 0) || (topic != null && topic.length() > 0)) { // need to make a message since last will is set Log.d(TAG, "Mqtt: setwill"); try { conOpt.setWill(topic, message.getBytes(), qos, retained); } catch (Exception e) { Log.e(TAG, "Mqtt: Can't set will", e); doConnect = false; //callback.onFailure(null, e); } } mMqttClient.setCallback(this); mMqttClient.setTraceCallback(this); if (doConnect) { MqttSettings.getInstance(mContext).setConnectedEnabled(true); try { Log.d(TAG, "Mqtt: connect to " + uri); mMqqtClientStatus = MqqtConnectionStatus.CONNECTING; mMqttClient.connect(conOpt, null, this); } catch (MqttException e) { Log.e(TAG, "Mqtt: connection error: ", e); } } } // endregion // region IMqttActionListener @Override public void onSuccess(IMqttToken iMqttToken) { if (mMqqtClientStatus == MqqtConnectionStatus.CONNECTING) { Log.d(TAG, "Mqtt connect onSuccess"); mMqqtClientStatus = MqqtConnectionStatus.CONNECTED; if (mListener != null) mListener.onMqttConnected(); MqttSettings settings = MqttSettings.getInstance(mContext); String topic = settings.getSubscribeTopic(); int topicQos = settings.getSubscribeQos(); if (settings.isSubscribeEnabled() && topic != null) { subscribe(topic, topicQos); } } else if (mMqqtClientStatus == MqqtConnectionStatus.DISCONNECTING) { Log.d(TAG, "Mqtt disconnect onSuccess"); mMqqtClientStatus = MqqtConnectionStatus.DISCONNECTED; if (mListener != null) mListener.onMqttDisconnected(); } else { Log.d(TAG, "Mqtt unknown onSuccess"); } } @Override public void onFailure(IMqttToken iMqttToken, Throwable throwable) { Log.d(TAG, "Mqtt onFailure. " + throwable); // Remove the auto-connect till the failure is solved if (mMqqtClientStatus == MqqtConnectionStatus.CONNECTING) { MqttSettings.getInstance(mContext).setConnectedEnabled(false); } // Set as an error mMqqtClientStatus = MqqtConnectionStatus.ERROR; String errorText = mContext.getString(R.string.mqtt_connection_failed)+". "+throwable.getLocalizedMessage(); Toast.makeText(mContext, errorText, Toast.LENGTH_LONG).show(); // Call listener if (mListener != null) mListener.onMqttDisconnected(); } // endregion // region MqttCallback @Override public void connectionLost(Throwable throwable) { Log.d(TAG, "Mqtt connectionLost. " + throwable); if (throwable != null) { // if disconnected because a reason show toast. Standard disconnect will have a null throwable Toast.makeText(mContext, R.string.mqtt_connection_lost, Toast.LENGTH_LONG).show(); } mMqqtClientStatus = MqqtConnectionStatus.DISCONNECTED; if (mListener != null) { mListener.onMqttDisconnected(); } } @Override public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { String message = new String(mqttMessage.getPayload()); if (message.length() > 0) { // filter cleared messages (to avoid duplicates) Log.d(TAG, "Mqtt messageArrived from topic: " + topic + " message: " + message + " isDuplicate: " + (mqttMessage.isDuplicate() ? "yes" : "no")); if (mListener != null) { mListener.onMqttMessageArrived(topic, mqttMessage); } // Fix duplicated messages clearing the received payload and processing only non null messages mqttMessage.clearPayload(); } } @Override public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { Log.d(TAG, "Mqtt deliveryComplete"); } // endregion // region MqttTraceHandler @Override public void traceDebug(String source, String message) { Log.d(TAG, "Mqtt traceDebug"); } @Override public void traceError(String source, String message) { Log.d(TAG, "Mqtt traceError"); } @Override public void traceException(String source, String message, Exception e) { Log.d(TAG, "Mqtt traceException"); } // endregion public interface MqttManagerListener { void onMqttConnected(); void onMqttDisconnected(); void onMqttMessageArrived(String topic, MqttMessage mqttMessage); } }