package com.nightscout.core.mqtt; import com.nightscout.core.dexcom.Utils; import com.nightscout.core.events.EventReporter; import com.nightscout.core.events.EventSeverity; import com.nightscout.core.events.EventType; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; 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.MqttSecurityException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import static net.tribe7.common.base.Preconditions.checkNotNull; public class MqttEventMgr implements MqttCallback, MqttPingerObserver, MqttMgrObservable, MqttTimerObserver { private final static String TAG = MqttEventMgr.class.getSimpleName(); protected final Logger log = LoggerFactory.getLogger(MqttEventMgr.class); private List<MqttMgrObserver> observers = new ArrayList<>(); private MqttClient client; private MqttConnectOptions options; private MqttTimer timer; private MqttPinger pinger; private long reconnectDelay = 10000L; private int defaultQOS = Constants.QOS_2; private EventReporter reporter; private MqttConnectionState state = MqttConnectionState.DISCONNECTED; private ResourceBundle messages = ResourceBundle.getBundle("MessagesBundle", Locale.getDefault()); private boolean shouldReconnect = false; public MqttEventMgr(MqttClient client, MqttConnectOptions options, MqttPinger pinger, MqttTimer timer, EventReporter reporter) { this.client = checkNotNull(client); this.options = checkNotNull(options); this.timer = checkNotNull(timer); this.pinger = checkNotNull(pinger); this.client.setCallback(MqttEventMgr.this); this.reporter = reporter; } public void setShouldReconnect(boolean shouldReconnect) { this.shouldReconnect = shouldReconnect; } public void connect() { try { log.info("MQTT connect issued"); client.connect(options); reporter.report(EventType.UPLOADER, EventSeverity.INFO, messages.getString("mqtt_connected")); if (!pinger.isActive()) { pinger.registerObserver(this); pinger.start(); } state = MqttConnectionState.CONNECTED; notifyOnConnect(); } catch (MqttSecurityException e) { // TODO: Determine how to notify user log.info("MQTT security issue"); reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_invalid_credentials")); if (timer.isActive()) { timer.deactivate(); } if (pinger.isActive()) { pinger.stop(); } } catch (MqttException e) { log.info("MQTT Exception {}", e); delayedReconnect(); } } public void delayedReconnect() { delayedReconnect(reconnectDelay); } public void delayedReconnect(long delayMs) { if (state == MqttConnectionState.RECONNECTING) { return; } // FIXME - reconnect flags are ugly. Seems to be a problem somewhere in the reconnect logic if (!shouldReconnect) { log.warn("Should not attempt to reconnect. Ignoring"); return; } log.info("MQTT delayed reconnect"); if (!timer.isActive()) { timer.registerObserver(this); timer.activate(); } timer.setTimer(delayMs); state = MqttConnectionState.RECONNECTING; log.info(messages.getString("mqtt_reconnect")); reporter.report(EventType.UPLOADER, EventSeverity.INFO, messages.getString("mqtt_reconnect")); } public void reconnect() { // if (pinger.isNetworkActive()) { log.info("MQTT issuing reconnect"); disconnect(); new Thread(new Runnable() { @Override public void run() { connect(); } }).start(); // } else { // reporter.report(EventType.UPLOADER, EventSeverity.ERROR, // messages.getString("mqtt_reconnect_fail")); // delayedReconnect(); // } } public void disconnect() { try { if (client.isConnected()) { client.disconnect(); log.info(messages.getString("mqtt_disconnected")); reporter.report(EventType.UPLOADER, EventSeverity.INFO, messages.getString("mqtt_disconnected")); } timer.deactivate(); pinger.stop(); } catch (MqttException e) { // TODO: determine how to handle this log.info(messages.getString("mqtt_disconnect_fail")); reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_disconnect_fail")); } state = MqttConnectionState.DISCONNECTED; } public void close() { try { disconnect(); client.close(); log.info(messages.getString("mqtt_close")); reporter.report(EventType.UPLOADER, EventSeverity.INFO, messages.getString("mqtt_close")); } catch (MqttException e) { // TODO: determine how to handle this. Cruton maybe? log.info(messages.getString("mqtt_close_fail")); reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_close_fail")); } pinger.unregisterObserver(this); timer.unregisterObserver(this); } @Override public void onFailedPing() { log.info(messages.getString("mqtt_ping_fail")); reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_ping_fail")); notifyOnDisconnect(); delayedReconnect(); } @Override public void timerUp() { reconnect(); } @Override public void connectionLost(Throwable cause) { // if (state != MqttConnectionState.CONNECTED) { // return; // } log.info(messages.getString("mqtt_lost_connection")); reporter.report(EventType.UPLOADER, EventSeverity.WARN, messages.getString("mqtt_lost_connection")); if (state != MqttConnectionState.RECONNECTING) { notifyOnDisconnect(); delayedReconnect(); } } public boolean isConnected() { return client.isConnected(); } public MqttClient getClient() { return client; } public MqttConnectionState getState() { return state; } @Override public void messageArrived(String topic, MqttMessage mqttMessage) { if (!mqttMessage.isDuplicate()) { notifyOnMessage(topic, mqttMessage); } pinger.reset(); } @Override public void deliveryComplete(IMqttDeliveryToken token) { pinger.reset(); } public void setReconnectDelay(long reconnectDelay) { this.reconnectDelay = reconnectDelay; } public void setDefaultQOS(int defaultQOS) { this.defaultQOS = defaultQOS; } public void subscribe(String... topics) { subscribe(defaultQOS, topics); } public void subscribe(int QOS, String... topics) { // List<String> mqTopics = Lists.newArrayList(topics); // List<String> mqTopics = new ArrayList<>(); // mqTopics.addAll(topics) boolean willReconnect = false; for (String topic : topics) { try { client.subscribe(topic, QOS); } catch (MqttException e) { // TODO: Determine what to do here reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_subscribe_fail")); log.info(e.getMessage()); // FIXME: a bit heavy handed here. Need to inspect error code to determine correct // actions if (!willReconnect) { this.delayedReconnect(); willReconnect = true; } } } } public void publish(byte[] message, String topic) { publish(message, topic, defaultQOS); } public void publish(byte[] message, String topic, int QOS) { try { client.publish(topic, message, QOS, true); reporter.report(EventType.UPLOADER, EventSeverity.INFO, messages.getString("mqtt_publish_success")); log.info("{}: Published \"{}\" to \"{}\"", MqttEventMgr.class.getSimpleName(), Utils.bytesToHex(message), topic); } catch (MqttException e) { // TODO: Determine what to do here reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_publish_fail")); this.delayedReconnect(); } } @Override public void registerObserver(MqttMgrObserver observer) { if (!observers.contains(observer)) { observers.add(observer); } } @Override public void unregisterObserver(MqttMgrObserver observer) { if (observers.contains(observer)) { observers.remove(observer); } } public int getNumberOfObservers() { return observers.size(); } @Override public void notifyOnMessage(String topic, MqttMessage message) { for (MqttMgrObserver observer : observers) { try { observer.onMessage(topic, message); } catch (Exception e) { // Horrible catch all but I don't want the manager to die due to an unhandled // exception from one of the observers. // TODO: Determine what to do here. Should send message to acra for further // analysis log.error(e.getMessage()); e.printStackTrace(); reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_observer_fail")); } } } @Override public void notifyOnDisconnect() { for (MqttMgrObserver observer : observers) { try { observer.onDisconnect(); } catch (Exception e) { // Horrible catch all but I don't want the manager to die due to an unhandled // exception from one of the observers. // TODO: Determine what to do here reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_observer_fail")); } } } @Override public void notifyOnConnect() { for (MqttMgrObserver observer : observers) { try { observer.onConnect(); } catch (Exception e) { // Horrible catch all but I don't want the manager to die due to an unhandled // exception from one of the observers. // TODO: Determine what to do here reporter.report(EventType.UPLOADER, EventSeverity.ERROR, messages.getString("mqtt_observer_fail")); } } } public MqttConnectOptions getOptions() { return options; } }