Java tutorial
/******************************************************************************* * Copyright (c) 2011 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.obiba.onyx.jade.instrument.ricelake; import gnu.io.CommPortIdentifier; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import gnu.io.UnsupportedCommOperationException; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.TooManyListenersException; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.UIManager; import org.obiba.onyx.jade.instrument.ExternalAppLauncherHelper; import org.obiba.onyx.jade.instrument.InstrumentRunner; import org.obiba.onyx.jade.instrument.LocalSettingsHelper; import org.obiba.onyx.jade.instrument.LocalSettingsHelper.CouldNotRetrieveSettingsException; import org.obiba.onyx.jade.instrument.LocalSettingsHelper.CouldNotSaveSettingsException; import org.obiba.onyx.jade.instrument.service.InstrumentExecutionService; import org.obiba.onyx.util.data.Data; import org.obiba.onyx.util.data.DataBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; public class RiceLakeWeightInstrumentRunner implements InstrumentRunner, InitializingBean { private Logger log = LoggerFactory.getLogger(RiceLakeWeightInstrumentRunner.class); private ResourceBundle resourceBundle; // Injected by spring. private InstrumentExecutionService instrumentExecutionService; protected ExternalAppLauncherHelper externalAppHelper; protected LocalSettingsHelper settingsHelper; private Locale locale; // Interface components private JFrame appWindow; private JButton saveButton; private MeasureCountLabel measureCountLabel; protected JTextField weightTxt; // Interface components dimension private int appWindowWidth; private int appWindowHeight; // Serial port configuration private ArrayList<String> availablePortNames; private Properties localSettings; /** * Lock used to block the main thread as long as the UI has not finished its job */ private final Object uiLock = new Object(); private boolean shutdown = false; private RiceLakeWeightComm rlComm; public RiceLakeWeightInstrumentRunner() throws Exception { super(); // Initialize interface components. saveButton = new JButton(); saveButton.setMnemonic('S'); weightTxt = new ResultTextField(); // Initialize interface components size appWindowWidth = 350; appWindowHeight = 175; localSettings = new Properties(); } @Override public void initialize() { if (!externalAppHelper.isSotfwareAlreadyStarted("tanitaInstrumentRunner")) { log.info("Refresh serial port list"); refreshSerialPortList(); log.info("Setup serial port"); setupSerialPort(); } else { JOptionPane.showMessageDialog(null, resourceBundle.getString("Err.Application_lock"), resourceBundle.getString("Title.Cannot_start_application"), JOptionPane.ERROR_MESSAGE); shutdown = true; } } @Override public void run() { if (!shutdown) { new Thread(new Runnable() { @Override public void run() { try { while (!shutdown) { if (rlComm != null) { rlComm.read(); } Thread.sleep(200); } } catch (Exception e) { } } }).start(); log.info("Starting Rice Lake GUI"); buildGUI(); // Obtain the lock outside the UI thread. This will block until the UI releases the lock, at which point it // should // be safe to exit the main thread. synchronized (uiLock) { try { uiLock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } log.info("Lock obtained. Exiting software."); } } @Override public void shutdown() { shutdown = true; if (rlComm != null) { try { log.info("Closing serial port"); rlComm.close(); } catch (Exception e) { // ignore log.info("Error Closing serial port"); } } } @Override public void afterPropertiesSet() throws Exception { // Attempt to retrieve settings persisted locally (if exist). try { localSettings = settingsHelper.retrieveSettings(); } catch (CouldNotRetrieveSettingsException e) { } log.info("Setting ricelakeweight-locale to {}", getLocale().getDisplayLanguage()); resourceBundle = ResourceBundle.getBundle("ricelakeweight-instrument", getLocale()); // Turn off metal's use of bold fonts UIManager.put("swing.boldMetal", Boolean.FALSE); appWindow = new JFrame(resourceBundle.getString("Title.RiceLakeWeight")); saveButton.setToolTipText(resourceBundle.getString("ToolTip.Save_and_return")); saveButton.setText(resourceBundle.getString("Save")); saveButton.setEnabled(false); } public Locale getLocale() { return locale; } public void setLocale(Locale locale) { this.locale = locale; } public String getComPort() { return localSettings.getProperty("comPort"); } public void setComPort(String tanitaCommPort) { localSettings.setProperty("comPort", tanitaCommPort); } public int getBaudeRate() { try { return Integer.parseInt(localSettings.getProperty("baudeRate")); } catch (NumberFormatException e) { return 9600; } } public void setBaudeRate(int baudeRate) { localSettings.setProperty("baudeRate", Integer.toString(baudeRate)); } public void setSettingsHelper(LocalSettingsHelper settingsHelper) { this.settingsHelper = settingsHelper; } public void setInstrumentExecutionService(InstrumentExecutionService instrumentExecutionService) { this.instrumentExecutionService = instrumentExecutionService; } public void setExternalAppHelper(ExternalAppLauncherHelper externalAppHelper) { this.externalAppHelper = externalAppHelper; } protected void buildGUI() { appWindow.setAlwaysOnTop(true); appWindow.setResizable(false); appWindow.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); appWindow.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { confirmOnExit(); } }); appWindow.add(buildMainPanel(), BorderLayout.CENTER); appWindow.pack(); appWindow.setSize(appWindowWidth, appWindowHeight); // Display the GUI in the middle of the screen. Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize(); appWindow.setLocation(SCREEN_SIZE.width / 2 - appWindowWidth / 2, SCREEN_SIZE.height / 2 - appWindowHeight / 2); clearData(); appWindow.setVisible(true); } /** * Puts together the GUI main panel component. * * @return */ protected JPanel buildMainPanel() { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.add(buildMeasureCountSubPanel()); panel.add(buildResultsSubPanel()); panel.add(buildActionButtonSubPanel()); return panel; } protected JPanel buildMeasureCountSubPanel() { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.add(measureCountLabel = new MeasureCountLabel()); panel.setAlignmentX(Component.LEFT_ALIGNMENT); return (panel); } protected JPanel buildResultsSubPanel() { final JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.add(new JLabel(resourceBundle.getString("Weight") + ":")); panel.add(Box.createRigidArea(new Dimension(10, 0))); panel.add(weightTxt); panel.add(Box.createRigidArea(new Dimension(10, 0))); panel.add(new JLabel(resourceBundle.getString("kg"))); panel.setAlignmentX(Component.LEFT_ALIGNMENT); return panel; } /** * Build action buttons sub panel */ protected JPanel buildActionButtonSubPanel() { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.add(Box.createHorizontalGlue()); panel.add(saveButton); JButton resetButton = new JButton(resourceBundle.getString("Reset")); resetButton.setMnemonic('R'); resetButton.setToolTipText(resourceBundle.getString("ToolTip.Reset_measurement")); panel.add(Box.createRigidArea(new Dimension(10, 0))); panel.add(resetButton); JButton cancelButton = new JButton(resourceBundle.getString("Cancel")); cancelButton.setMnemonic('A'); cancelButton.setToolTipText(resourceBundle.getString("ToolTip.Cancel_measurement")); panel.add(Box.createRigidArea(new Dimension(10, 0))); panel.add(cancelButton); JButton configureButton = new JButton(resourceBundle.getString("Configure")); configureButton.setMnemonic('C'); configureButton.setToolTipText(resourceBundle.getString("ToolTip.Configure")); panel.add(Box.createRigidArea(new Dimension(10, 0))); panel.add(configureButton); // Configure button listener configureButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { configure(); } }); // Reset button listener resetButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { clearData(); } }); // Save button listener. saveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sendOutputToServer(); } }); // Cancel button listener. cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { confirmOnExit(); } }); panel.setAlignmentX(Component.LEFT_ALIGNMENT); return (panel); } class MeasureCountLabel extends JLabel { private static final long serialVersionUID = 1L; public MeasureCountLabel() { super(); } @Override public String getText() { return resourceBundle.getString("MeasureCount.Measures") + ": " + instrumentExecutionService.getCurrentMeasureCount() + " " + resourceBundle.getString("MeasureCount.saved") + ", " + instrumentExecutionService.getExpectedMeasureCount() + " " + resourceBundle.getString("MeasureCount.expected") + "."; } } public void sendOutputToServer() { log.info("Sending output of Rice Lake Weight to server..."); Map<String, Data> output = new HashMap<String, Data>(); output.put("Weight", DataBuilder.buildDecimal(weightTxt.getText())); instrumentExecutionService.addOutputParameterValues(output); saveButton.setEnabled(false); appWindow.repaint(); log.info("Sending output of Rice Lake Weight to server done..."); clearData(); if (instrumentExecutionService.getExpectedMeasureCount() <= instrumentExecutionService .getCurrentMeasureCount()) { exitUI(); } } /** * Displays a confirmation window when the application is closed by the user without saving. */ protected void confirmOnExit() { // Ask for confirmation only if data has been fetch from the device. if (saveButton.isEnabled()) { int wConfirmation = JOptionPane.showConfirmDialog(appWindow, resourceBundle.getString("Confirmation.Close_window"), resourceBundle.getString("Title.Confirmation"), JOptionPane.YES_NO_OPTION); // If confirmed, application is closed. if (wConfirmation == JOptionPane.YES_OPTION) { exitUI(); } } else { exitUI(); } } /** * Signals that the UI has finished its job. */ protected void exitUI() { appWindow.setVisible(false); synchronized (uiLock) { uiLock.notify(); } shutdown = true; } @SuppressWarnings("unchecked") protected void refreshSerialPortList() { log.info("Refreshing serial port list..."); Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers(); availablePortNames = new ArrayList<String>(); // Build a list of all serial ports found. while (portEnum != null && portEnum.hasMoreElements()) { CommPortIdentifier port = portEnum.nextElement(); if (port.getPortType() == CommPortIdentifier.PORT_SERIAL) { log.info("Port name={}, Port type={}", port.getName(), port.getPortType()); availablePortNames.add(port.getName()); } } } private void configure() { // List all serial port in a drop down list, so a new one can be // selected. refreshSerialPortList(); String selectedPort = (String) JOptionPane.showInputDialog(appWindow, resourceBundle.getString("Instruction.Choose_port"), resourceBundle.getString("Title.Settings"), JOptionPane.QUESTION_MESSAGE, null, availablePortNames.toArray(), getComPort()); if (selectedPort != null) { setComPort(selectedPort); try { settingsHelper.saveSettings(localSettings); } catch (CouldNotSaveSettingsException e) { log.error("Local settings could not be persisted.", e); } setupSerialPort(); } } /** * Establish the connection with the device connected to the serial port. */ public void setupSerialPort() { try { // If port already open, close it. if (rlComm != null) { rlComm.close(); rlComm = null; } // Initialize serial port attributes. log.info("Fetching communication port {}", getComPort()); CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(getComPort()); log.info("Opening communication port {}", getComPort()); SerialPort serialPort = (SerialPort) portId.open("OBiBa Onyx Rice Lake Weight Reader", 2000); // Make sure the port is "Clear To Send" rlComm = new RiceLakeWeightComm(serialPort, getBaudeRate()); } catch (Exception e) { rlComm = null; log.warn("Could not access the specified serial port."); } } private void clearData() { weightTxt.setText(""); if (rlComm != null) { rlComm.reset(); } saveButton.setEnabled(false); measureCountLabel.repaint(); } private class RiceLakeWeightComm implements SerialPortEventListener { private final byte[] ZERO_COMMAND = new byte[] { 'z' }; private final byte[] READ_COMMAND = new byte[] { 'p' }; private final SerialPort serialPort; private final BufferedReader bufferedReader; private final OutputStream os; public RiceLakeWeightComm(SerialPort serialPort, int baudRate) throws UnsupportedCommOperationException, TooManyListenersException, IOException { this.serialPort = serialPort; serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.addEventListener(this); serialPort.notifyOnDataAvailable(true); serialPort.notifyOnCTS(true); serialPort.notifyOnDSR(true); this.bufferedReader = new BufferedReader(new InputStreamReader(serialPort.getInputStream())); this.os = serialPort.getOutputStream(); } public void reset() { try { send(ZERO_COMMAND); } catch (IOException e) { log.info("Failed sending reset command to instrument."); } } public void read() { try { send(READ_COMMAND); } catch (IOException e) { log.info("Failed sending read command to instrument."); } } public void close() { this.serialPort.close(); } public void send(byte[] command) throws IOException { // log.debug("Sending {}", command); os.write(command); } @Override public void serialEvent(SerialPortEvent event) { switch (event.getEventType()) { // Clear to send case SerialPortEvent.CTS: log.debug("CTS"); break; // Data is available at the serial port, so read it... case SerialPortEvent.DATA_AVAILABLE: try { if (bufferedReader.ready()) { // Parse and sets the data in the GUI. String response = bufferedReader.readLine().trim(); // log.info("data={}", response); parseResponse(response); // Enable save button, so data can be saved. saveButton.setEnabled(!weightTxt.getText().equals("0.0")); } } catch (IOException wErrorReadingDataOnSerialPort) { JOptionPane.showMessageDialog(appWindow, resourceBundle.getString("Err.Result_communication"), resourceBundle.getString("Title.Communication_error"), JOptionPane.ERROR_MESSAGE); } break; // Data set ready case SerialPortEvent.DSR: log.debug("DSR"); break; } } private void parseResponse(String response) { String[] values = response.split(" "); Double weight = Double.parseDouble(values[0]); if (values[1].equals("lb")) { weight = ((double) Math.round(weight * 0.45359237 * 10)) / 10; } weightTxt.setText(weight.toString()); } } private class ResultTextField extends JTextField { private static final long serialVersionUID = 1L; public ResultTextField() { super(); this.setEditable(false); this.setSelectionColor(Color.WHITE); this.setBackground(Color.WHITE); this.setHorizontalAlignment(JTextField.RIGHT); // this.setPreferredSize(new Dimension(30, 0)); } } }