org.eclipse.paho.sample.utility.MQTTFrame Maven / Gradle / Ivy
/********************************************************************************
* Copyright (c) 2002, 2013 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Description: Contains the main method for starting the MQTT swing user interface
*
* Contributors:
* Ian Harwood, Ian Craggs - initial API and implementation and/or initial documentation
********************************************************************************/
package org.eclipse.paho.sample.utility;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.EtchedBorder;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
/**
* This class is the controlling class for the application. It contains the main method
* for launching the Swing GUI and itcontrols the MQTT connection.
*/
public class MQTTFrame implements ActionListener, MqttCallback, Runnable {
private JPanel connPanel = new JPanel();
private JPanel pubPanel = new JPanel();
private JPanel subPanel = new JPanel();
private PubPanel pubPanelContr;
private SubPanel subPanelContr;
// Main Components
private ConnOpts optionsComp = null;
private MQTTHist historyComp = null;
private JPanel mqttComp = null;
private JComboBox ipAddress;
private JComboBox port;
private LED led;
private JButton connect;
private JButton disconnect;
private MqttClient mqtt = null;
private MqttConnectOptions opts = null;
private boolean connected = false;
private boolean traceEnabled = false;
private Object connLostWait = new Object(); // Object to coordinated ConnectionLost and disconnect threads if
// disconnect is hit during connectionLost
private JFrame frame= null;
/**
* Constant controlling the display of JTextFields
*/
protected static final Dimension TEXT_FIELD_DIMENSION = new Dimension( 1000, 20 );
/**
* Constant controlling the display of JComboBoxes
*/
protected static final Dimension DROP_DOWN_DIMENSION = new Dimension(35, 20);
protected static final Insets TEXT_MARGINS = new Insets(3,3,3,3);
protected static final int FRAME_WIDTH = 375;
protected static final int FRAME_HEIGHT = 450;
// The name of the properties file
private final static String PROP_FILE = "mqtt.properties";
private final static String PROP_DELIM = ";";
// Other constants
private static final String DEFAULT_IP_ADDRESS = "127.0.0.1";
private static final String DEFAULT_PORT_NUMBER = "1883";
/**
* Constructor for MQTTFrame
*/
public MQTTFrame() {
super();
}
/**
* The main method for launching the GUI. The main method takes no arguments.
*
* This method attaches a WindowListener to the window to detect windowClosing events. When
* such an event is detected then various parameters are gathered from the GUI and written
* to a properties file before the application exits.
* @param args No arguments are required.
*/
public static void main(String[] args) {
JFrame theFrame = null;
final MQTTFrame view = new MQTTFrame();
theFrame = view.getJFrame();
theFrame.setSize( MQTTFrame.FRAME_WIDTH, MQTTFrame.FRAME_HEIGHT );
theFrame.setLocation(150, 50);
theFrame.setResizable(true);
view.init( theFrame.getContentPane() );
theFrame.setVisible(true);
theFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// Don't cleanly disconnect when the window is closed.
// This allows things like Last Will & Testament to be easily tested by
// closing the window to unexpectedly terminate the MQTT session.
//view.disconnect();
// Write the properties to disk before exiting
FileOutputStream propFile = null;
Properties props = new Properties();
try {
propFile = new FileOutputStream( PROP_FILE );
// First populate the properties object
// IPADDRESS
props.setProperty("IPAddressList", view.constructPropertyValue("IPAddressList") );
// IPPORT
props.setProperty("IPPortList", view.constructPropertyValue("IPPortList") );
// CLIENTID
props.setProperty("ClientId", view.constructPropertyValue("ClientId") );
// PERSISTENCE ENABLED
props.setProperty("Persistence", view.constructPropertyValue("Persistence") );
// PERSISTENCE DIRECTORY
props.setProperty("PersistenceDir", view.constructPropertyValue("PersistenceDir") );
// Now write to disk
props.store( propFile, "MQTT Utility properties" );
} catch(Exception fe) {
// If we cannot create a properties file then don't worry
}
System.exit(0);
}
});
}
/**
* @return This MQTTFrame's JFrame object reference.
*/
private JFrame getJFrame() {
if ( frame == null ) {
frame = new JFrame();
}
return frame;
}
/**
* This method builds all the components to add to the
* content pane of the JFrame window. It builds the connect panel itself and delegates building
* of the publish and subscribe panels to the PubPanel and SubPanel objects respectively.
* It also loads the properties file and gives the window a Windows look and feel.
* @param contentPane - The content pane of the JFrame window.
*/
protected void init( Container contentPane ) {
FileInputStream propFile = null;
Properties props = new Properties();
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch ( Exception ex) {
// Don't worry if we can't set the look and feel
}
// Does a properties file exist from which the GUI can be populated?
try {
propFile = new FileInputStream( PROP_FILE );
props.load( propFile );
} catch(Exception fe) {
// If we can't find a properties file then don't worry
propFile = null;
}
// Now build the GUI components
setTitleText("");
connPanel.setLayout(new GridLayout(2, 1));
connPanel.setBorder( new EtchedBorder() );
// Build the main components to add to the tabbed pane.
mqttComp = new JPanel( new BorderLayout() );
historyComp = new MQTTHist( getJFrame(), optionsComp );
// Add the panel which handles connecting and disconnecting from the broker
mqttComp.add(connPanel, BorderLayout.NORTH);
// Add the panels for publish and subscribe to a JSplitPane
// The JSplitPane allows the panels to be resized evenly, allows the user to resize the
// panels manually and provides a good layout for two similar panels
JSplitPane pubsub = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true,
subPanel, pubPanel );
pubsub.setOneTouchExpandable(true); // Allow either panel to be expanded to full size easily
pubsub.setDividerSize(10); // Set the divider size large enough to display the one touch expandable arrows
pubsub.setResizeWeight(0.5); // Resize evenly
// Add the pubsub JSplitPane to the outer frame
mqttComp.add( pubsub, BorderLayout.CENTER );
ipAddress = new JComboBox();
ipAddress.setPreferredSize( new Dimension(125,20) );
ipAddress.setEditable( true );
// Load any TCP/IP address info from the config file
getProperties( props, ipAddress, "IPAddressList", DEFAULT_IP_ADDRESS );
port = new JComboBox();
port.setPreferredSize( new Dimension(65,20) );
port.setEditable( true );
// Load any TCP/IP port info from the config file
getProperties( props, port, "IPPortList", DEFAULT_PORT_NUMBER );
// Create the options panel
optionsComp = new ConnOpts( this, props );
// Add an LED to indicate the connection state
led = new LED();
led.setRed();
new Thread(led).start();
connect = new JButton( "Connect" );
disconnect = new JButton( "Disconnect" );
disconnect.setEnabled(false);
JButton history = new JButton("History");
connect.addActionListener(this);
disconnect.addActionListener(this);
history.addActionListener(this);
JPanel text = new JPanel();
text.add( new JLabel("Broker TCP/IP address: ") );
text.add( ipAddress );
text.add( port );
JPanel buttons = new JPanel();
buttons.setLayout( new BoxLayout( buttons, BoxLayout.X_AXIS ) );
buttons.add( new JSeparator( SwingConstants.VERTICAL ) );
buttons.add( led );
buttons.add( connect );
buttons.add( new JLabel(" ") );
buttons.add( disconnect );
buttons.add( new JSeparator( SwingConstants.VERTICAL ) );
buttons.add( history );
buttons.add( new JSeparator( SwingConstants.VERTICAL ) );
connPanel.add( text );
connPanel.add( buttons );
pubPanelContr = new PubPanel( pubPanel, this );
subPanelContr = new SubPanel( subPanel, this );
if ( propFile != null ) {
try {
propFile.close();
} catch(Exception e) {
// Don't worry if we can't close the properties file
}
}
// Now construct the tabbed pane
JTabbedPane tabbedGui = new JTabbedPane();
tabbedGui.addTab( "MQTT", mqttComp );
tabbedGui.addTab( "Options", optionsComp );
contentPane.add( tabbedGui );
}
/**
* A simple wrapper for the MQTT publish method. This method is invoked as a result of the
* publish button being pressed. If a problem is detected then an exception is thrown and an error
* message is displayed in the window title bar or in a separate dialog box.
* @param topic The topic on which the data will be published.
* @param message The data to be published
* @param qos The Quality of Service at which the publication should be delivered.
* @param retained Is this a retained publication or not?
*/
public void publish( String topic, byte[] message, int qos, boolean retained ) throws Exception {
setTitleText( "" );
if ( connected ) {
try {
mqtt.getTopic(topic).publish( message, qos, retained );
} catch ( MqttException ex ) {
setTitleText( "MQTT publish exception !" );
JOptionPane.showMessageDialog( frame, ex.getClass().getName() + "\n" + ex.getMessage(), "MQTT Publish Exception", JOptionPane.ERROR_MESSAGE );
throw ex;
}
} else {
setTitleText( "MQTT client not connected !" );
throw new Exception( "MQTT client not connected" );
}
}
/**
* A wrapper for the MQTT disconnect method.
* As well as disconnecting the protocol this method enables / disables buttons
* as appropriate when in disconnected state. It also sets the correct colour of the indicator LED.
*/
public void disconnect() {
connected = false;
// Notify connectionLost to give up. It may be running..
synchronized(connLostWait) {
connLostWait.notify();
}
// Disconnect from the broker
if ( mqtt != null ) {
try {
mqtt.disconnect();
} catch ( Exception ex ) {
setTitleText( "MQTT disconnect error !" );
ex.printStackTrace();
System.exit(1);
}
}
// Set the LED state correctly
// If the led is flashing then turn it off
// This only occurs if disconnect is hit during connection lost
if ( led.isFlashing() ) {
led.setFlash();
}
led.setRed();
setConnected( false );
synchronized(this) { // Grab the log synchronisation lock
writeLogln("WebSphere MQ Telemetry transport disconnected" );
}
}
/**
* A wrapper for the MQTT connect method. If the ip address, port number or persistence flag
* has changed since the last time then a new MqttClient object is required. If these values haven't changed then
* any previously created object can be used.
* Check whether Last Will & Testament is required and call the appropriate connect method. The only persistence implementation supported at the moment is
* MqttFilePersistence.
* @param ipAddr The IP address or hostname to connect to.
* @param port The IP port number to connect to.
* @param usePersistence Is persistence required?
*/
public void connect( String connStr, boolean usePersistence ) throws MqttException {
// Connect to the broker
// If we have a MqttClient object and the new ip address
// or port number is not equal to the previous, or the persistence flag changes between
// off and on then we need a new object.
if ( (mqtt != null) &&
(!connStr.equals(mqtt.getServerURI()) /*||
(usePersistence != (mqtt.getPersistence() != null) )*/ ) ) {
//mqtt.terminate();
mqtt = null;
}
if ( mqtt == null ) {
MqttClientPersistence persistence = null;
if ( usePersistence ) {
persistence = new MqttDefaultFilePersistence( optionsComp.getPersistenceDirectory() );
}
mqtt = new MqttClient( connStr, optionsComp.getClientID(), persistence );
mqtt.setCallback( this );
// Carry the trace setting over to the new MqttClient object
if ( traceEnabled ) {
startTrace();
}
}
// Set the retry interval for the connection
//mqtt.setRetry( optionsComp.getRetryInterval() );
opts = new MqttConnectOptions();
opts.setCleanSession(optionsComp.isCleanSessionSelected());
opts.setKeepAliveInterval(optionsComp.getKeepAlive());
if ( optionsComp.isLWTTopicSet() ) {
opts.setWill(mqtt.getTopic(optionsComp.getLWTTopic()),
optionsComp.getLWTData().getBytes(),
optionsComp.getLWTQoS(),
optionsComp.isLWTRetainSelected());
}
mqtt.connect(opts);
}
/**
* This method is called when either the subscribe or unsubscribe buttons are pressed. It performs the
* MQTT subscribe or unsubscribe and writes an entry in the history log if the history window is open.
* Any exceptions are caught and displayed in a dialog box.
* @param topic The topic to subscribe to
* @param qos The maximum Quality of Service at which to receive publications
* @param sub Is this a subscribe or unsubscribe operation? (true if subscribe).
*/
public void subscription( String topic, int qos, boolean sub ) {
setTitleText( "" );
if ( connected ) {
try {
String[] theseTopics = new String[1];
int[] theseQoS = new int[1];
theseTopics[0] = topic;
theseQoS[0] = qos;
synchronized(this) { // Grab the log synchronisation lock
if ( sub ) {
writeLogln( " --> SUBSCRIBE, TOPIC:" + topic + ", Requested QoS:" + qos );
} else {
writeLogln( " --> UNSUBSCRIBE, TOPIC:" + topic );
}
}
if ( sub ) {
mqtt.subscribe( theseTopics, theseQoS );
} else {
mqtt.unsubscribe( theseTopics );
}
} catch ( Exception ex ) {
setTitleText( "MQTT subscription exception caught !" );
JOptionPane.showMessageDialog( frame, ex.getMessage(), "MQTT Subscription Exception", JOptionPane.ERROR_MESSAGE );
}
} else {
setTitleText( "MQTT client not connected !" );
}
}
/** Invoked by actionPerformed when connect is pressed.
* This allows actionPerformed to return and paint the window. This thread
* then does the MQTT connect to the broker.
* This method also ensures that the LED colour is set correctly and writes
* an entry to the history dialog if it is open.
*/
public void run() {
int rc = -1;
// Connect to the broker
String ipAddr = (String)ipAddress.getSelectedItem();
String portNum = (String)port.getSelectedItem();
String connStr = "";
try {
// If the entry in the IP Address drop down list contains '://' then assume
// the connection has been explicitly entered as tcp://ip_address:port or local://broker_name.
// Otherwise read the ip address and port number from their respective drop downs.
if ( ipAddr.indexOf("://") < 0 ) {
connStr = "tcp://" + ipAddr + ":" + portNum;
} else {
connStr = ipAddr;
}
connect( connStr, optionsComp.isPersistenceSelected() );
// Successful connect(no exception). Remember the ipAddress and port in the drop downs
updateComboBoxList( ipAddress, ipAddr );
updateComboBoxList( port, portNum );
connected = true;
led.setGreen();
setConnected( true );
} catch( NumberFormatException nfe ) {
JOptionPane.showMessageDialog( frame, "Invalid port number !", "MQTT Connect Exception", JOptionPane.ERROR_MESSAGE );
} catch ( MqttException mqe ) {
setTitleText( "MQTT connect failed !" );
Throwable e = mqe.getCause();
String msg = "";
if ( e == null ) {
e = mqe;
} else if ( mqe.getMessage() != null ) {
msg += mqe.getMessage() + "\n";
}
msg += e;
JOptionPane.showMessageDialog( frame, msg, "MQTT Connect Exception", JOptionPane.ERROR_MESSAGE );
e.printStackTrace();
} catch ( Exception ex ) {
setTitleText( "MQTT connect failed !" );
JOptionPane.showMessageDialog( frame, ex, "MQTT Connect Exception", JOptionPane.ERROR_MESSAGE );
ex.printStackTrace();
}
if ( !connected ) {
led.setRed();
setConnected( false );
}
synchronized(this) { // Grab the log synchronisation lock
if ( connected ) {
writeLogln("WebSphere MQ Telemetry transport connected to " + mqtt.getServerURI() );
} else {
writeLogln("ERROR:WebSphere MQ Telemetry transport failed to connect to " + connStr );
}
}
}
/**
* Implement the ActionListener interface and catch user interface events. Button pressed events handled are as follows:
*
- Connect - Check the client is not already connected, set the LED colour to amber to indicate connect is in progress and
* start a thread to do the connect.
*
- Disconnect - Check the client is connected and then disconnect the MQTT protocol
*
- Otions - If the options dialog is opened then reset it's size and position to default values before making it visible
*
* @param e The action event to process.
*/
public void actionPerformed( ActionEvent e) {
setTitleText( "" );
if ( e.getActionCommand().equals("Connect") ) {
// When the connect button is pressed we are either connected or not connected
// If we are connected then say so.
// If we are not connected then
// 1. Set the LED to Amber, the state to connecting and start a thread to do the actual connect.
// This allows the GUI thread to return and paint the window correctly
if ( connected ) {
// Already connected
setTitleText( "MQTT session already active !" );
} else {
// Initialise the GUI prior to connecting by setting the LED to amber.
// Start a thread to do the connect.
connect.setEnabled(false);
led.setAmber();
connected = false;
new Thread(this).start();
}
} else if ( e.getActionCommand().equals("Disconnect") ) {
if ( connected ) {
// Disconnect from the broker
disconnect();
} else {
setTitleText( "MQTT client not connected !" );
}
} else if ( e.getActionCommand().equals("History") ) {
historyComp.enableHistory();
}
}
/**
* This method accepts a string on text and displays it in the window's title bar.
* @param extraText The text to be appended to some default words and displayed.
*/
// Synchronized as this may also be called on the connectionLost thread, which is
// created by the Java MQTT Client
public synchronized void setTitleText( String extraText ) {
if ( extraText.equals("") ) {
frame.setTitle( "Paho MQTT Utility" );
} else {
frame.setTitle( "Paho MQTT Utility - " + extraText );
}
}
/**
* The method is part of the MqttSimpleCallback interface
*
In the event of the MQTT connection being broken the LED is set to colour amber and made to flash.
* The code then keeps trying to reconnect until either a successful
* reconnect occurs or the disconnect button is pressed. Finally the LED is stopped flashing and set to
* green or red depending upon whether the connect was successful or not.
*/
public void connectionLost(java.lang.Throwable cause) {
int rc = -1;
// Flip the LED to Amber and set it flashing
led.setAmber();
led.setFlash();
setTitleText( "Connection Lost!....Reconnecting" );
synchronized(this) { // Grab the log synchronisation lock
writeLogln( "MQTT Connection Lost!....Reconnecting to " + mqtt.getServerURI() );
}
try {
// While we have failed to reconnect and disconnect hasn't
// been called by another thread retry to connect
while ( (rc == -1) && connected ) {
try {
synchronized(connLostWait) {
connLostWait.wait(10000);
}
} catch (InterruptedException iex) {
// Don't care if we are interrupted
}
synchronized(this) { // Grab the log synchronisation lock
if ( connected ) {
writeLog( "MQTT reconnecting......" );
try {
connect( mqtt.getServerURI(), optionsComp.isPersistenceSelected() );
rc = 0;
} catch (MqttException mqte) {
// Catch any MQTT exceptions, set rc to -1 and retry
rc = -1;
}
if ( rc == -1 ) {
writeLogln( "failed" );
} else {
writeLogln( "success !" );
}
}
}
}
// Remove title text once we have reconnected
setTitleText( "" );
} catch (Exception ex) {
setTitleText( "MQTT connection broken !" );
ex.printStackTrace();
disconnect();
//throw ex;
} finally {
// Set the flashing off whatever happens
if ( led.isFlashing() ) {
led.setFlash(); // Flash off
}
}
// If we get here and we are connected then set the led to green
if ( connected ) {
led.setGreen();
setConnected( true );
} else {
led.setRed();
setConnected( false );
}
}
/**
* The method is part of the MqttCallback interface
* Pass the message as is to the SubPanel object which will display it.
*/
public void messageArrived( String topic, MqttMessage message) {
try {
subPanelContr.updateReceivedData( topic, message.getPayload(), message.getQos(), message.isRetained() );
}
catch (Exception e)
{
}
}
public void deliveryComplete( IMqttDeliveryToken token ) {
}
/**
* If a new topic is typed into the publish topic box then the
* subscribe topic box needs to be updated.
* @param topicName The topic name to add to the subscribe topic list
*/
public void updateSubscribeTopicList( String topicName ) {
subPanelContr.updateTopicList( topicName );
}
/**
* If a new topic is typed into the subscribe topic box then the
* publish topic box needs to be updated.
* @param topicName The topic name to add to the publish topic list
*/
public void updatePublishTopicList( String topicName ) {
pubPanelContr.updateTopicList( topicName );
}
/**
* This method handles string items in JComboBox drop downs. It checks
* to see if an item already exists in the list. If it doesn't, then it updates the list.
* @param list The JComboBox object to update
* @param itemName The value to add into the list if it doesn't already exist
* @return True if the item was added to the list
*/
public boolean updateComboBoxList( JComboBox list, String itemName ) {
int listCount = list.getItemCount();
boolean found = false;
if ( itemName == null ) {
return false;
}
for( int i=0; i 0) {
retString = "";
} else {
retString = DEFAULT_IP_ADDRESS;
}
for( int i=0; i < numAddrs; i++ ) {
retString += ipAddress.getItemAt(i);
// Don't add a delimiter after the last token
if ( i != numAddrs - 1 ) {
retString += PROP_DELIM;
}
}
} else if ( prop.equals("IPPortList") ) {
int numPorts = port.getItemCount();
if (numPorts > 0) {
retString = "";
} else {
retString = DEFAULT_PORT_NUMBER;
}
for( int i=0; i < numPorts; i++ ) {
retString += port.getItemAt(i);
// Don't add a delimiter after the last token
if ( i != numPorts - 1 ) {
retString += PROP_DELIM;
}
}
} else if ( prop.equals("ClientId") ) {
retString = optionsComp.getClientID();
} else if ( prop.equals("Persistence") ) {
retString = String.valueOf( optionsComp.isPersistenceSelected() );
} else if ( prop.equals("PersistenceDir") ) {
retString = optionsComp.getPersistenceDirectory();
}
return retString;
}
/**
* If we are not connected then disable the disconnect, publish,
* subscribe, and unsubscribe buttons. Enable connect.
* If we are connected then do the inverse.
* @param b True if connected, false otherwise.
*/
private void setConnected( boolean b ) {
pubPanelContr.enableButtons( b );
subPanelContr.enableButtons( b );
disconnect.setEnabled( b );
connect.setEnabled( !b );
}
}