/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * */ package org.myrobotlab.control; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import java.util.List; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultCaret; import javax.swing.text.Document; import org.myrobotlab.codec.serial.Codec; import org.myrobotlab.codec.serial.DecimalCodec; import org.myrobotlab.image.Util; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.service.GUIService; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.Serial; import org.python.netty.handler.codec.CodecException; import org.slf4j.Logger; public class SerialGUI extends ServiceGUI implements ActionListener, ItemListener { static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(SerialGUI.class); // menu JComboBox<String> reqFormat = new JComboBox<String>(new String[] { "decimal", "hex", "ascii", "arduino" }); JComboBox<String> ports = new JComboBox<String>(); JButton refresh = new JButton("refresh"); JButton createVirtualUART = new JButton("create virtual uart"); JButton record = new JButton(); // JButton sendTx = new JButton("send tx from file"); JButton connectButton = new JButton(); JLabel connectLight = new JLabel(); JTextArea rx = new JTextArea(20, 10); JLabel rxTotal = new JLabel("0"); JLabel txTotal = new JLabel("0"); String delimiter = " "; Integer width = 16; JTextField widthMenu = new JTextField("16"); int rxCount = 0; int txCount = 0; // JTextField sendData = new JTextField(40); JTextArea tx = new JTextArea(10, 60); JButton send = new JButton("send"); JButton sendFile = new JButton("send file"); Serial mySerial = null; final SerialGUI myself; // gui's formatters Codec rxFormatter = new DecimalCodec(myService); Codec txFormatter = new DecimalCodec(myService); // TODO // save data to file button // send file // create virtual port // create null modem cable public SerialGUI(final String boundServiceName, final GUIService myService, final JTabbedPane tabs) { super(boundServiceName, myService, tabs); myself = this; mySerial = (Serial) Runtime.getService(boundServiceName); } @Override public void actionPerformed(ActionEvent e) { Object o = e.getSource(); if (o == record) { if (record.getText().startsWith("record")) { send("record"); send("broadcastState"); } else { send("stopRecording"); send("broadcastState"); } } if (o == connectButton) { // TODO: make this connect/disconnect if (mySerial.isConnected()) { mySerial.disconnect(); connectButton.setText("Connect"); } else { try { mySerial.open((String) ports.getSelectedItem()); connectButton.setText("Disconnect"); } catch (Exception e2) { myService.error("could not connect"); log.error("connect in gui threw", e2); } } } if (o == createVirtualUART) { send("createVirtualUART"); } if (o == refresh) { send("refresh"); } if (o == sendFile) { JFileChooser fileChooser = new JFileChooser(); // set current directory fileChooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); int result = fileChooser.showOpenDialog(this.getDisplay()); if (result == JFileChooser.APPROVE_OPTION) { // user selects a file File selectedFile = fileChooser.getSelectedFile(); send("writeFile", selectedFile.getAbsolutePath()); } } if (o == send) { String data = tx.getText(); send("write", data.getBytes()); myService.info("sent [%s]", data); } } @Override public void attachGUI() { subscribe("publishRX", "publishRX", Integer.class); subscribe("publishTX", "publishTX", Integer.class); subscribe("publishState", "getState", Serial.class); subscribe("getPortNames", "onPortNames", List.class); // forces scan of ports send("refresh"); send("getPortNames"); } public void autoScroll(boolean b) { DefaultCaret caret = (DefaultCaret) rx.getCaret(); if (b) { caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); } else { caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); } DefaultCaret caretTX = (DefaultCaret) tx.getCaret(); if (b) { caretTX.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); } else { caretTX.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); } } @Override public void detachGUI() { unsubscribe("publishRX", "publishRX", String.class); unsubscribe("publishTX", "publishTX", String.class); unsubscribe("publishState", "getState", Serial.class); } public void onPortNames(final List<String> inPorts) { ports.removeAllItems(); ports.addItem(""); for (int i = 0; i < inPorts.size(); ++i) { ports.addItem(inPorts.get(i)); } } /** * the gui is no simplified - a single broadcastState() -> getState(Serial) * is used to propegate all data which needs updating. Since that is the * case a single invokeLater is used. It is unadvised to have more * invokeLater in other methods as race conditions are possible * * @param serial */ public void getState(final Serial serial) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { // prevent re-firing the event :P reqFormat.removeItemListener(myself); ports.removeItemListener(myself); mySerial = serial; // refresh all the ports in the combo box onPortNames(serial.getPortNames()); // set the appropriate status // ie connection value and current port setPortStatus(); // WARNING // don't use the same output formatters as the serial // service // they might have a different state when they are writing // out to a file... String key = mySerial.getRXCodecKey(); if (key != null && !key.equals(rxFormatter.getKey())) { // create new formatter from type key rxFormatter = Codec.getDecoder(key, myService); // TODO - set the reqTXFormat box .. too lazy :P - // hopefully reqFormat.setSelectedItem(key); } key = mySerial.getTXCodecKey(); if (key != null && !key.equals(txFormatter.getKey())) { // create new formatter from type key txFormatter = Codec.getDecoder(key, myService); } ports.addItemListener(myself); reqFormat.addItemListener(myself); } catch (Exception e) { Logging.logError(e); } if (!serial.isRecording()) { // captureRX.setText(serial.getRXFileName()); } else { record.setText("record"); } else { record.setText("stop recording"); } if (serial.isConnected()) { connectButton.setText("Disconnect"); } else { connectButton.setText("Connect"); } // if (mySerial.getRXFormatter()) } }); } @Override public void init() { display.setLayout(new BorderLayout()); JPanel north = new JPanel(); north.add(new JLabel("port ")); north.add(ports); north.add(refresh); north.add(connectLight); north.add(new JLabel(" ")); north.add(reqFormat); north.add(new JLabel("width ")); north.add(widthMenu); north.add(createVirtualUART); north.add(record); north.add(connectButton); // north.add(sendTx); display.add(north, BorderLayout.NORTH); rx.setEditable(false); JScrollPane scrollPanelRX = new JScrollPane(rx); JScrollPane scrollPanelTX = new JScrollPane(tx); autoScroll(true); display.add(scrollPanelRX, BorderLayout.CENTER); JPanel south = new JPanel(); south.add(scrollPanelTX); south.add(send); south.add(sendFile); south.add(new JLabel("rx")); south.add(rxTotal); south.add(new JLabel("tx")); south.add(txTotal); display.add(south, BorderLayout.SOUTH); createVirtualUART.addActionListener(this); send.addActionListener(this); sendFile.addActionListener(this); record.addActionListener(this); connectButton.addActionListener(this); reqFormat.addItemListener(this); refresh.addActionListener(this); // zod ports.addItemListener(this); } // onChange of ports @Override public void itemStateChanged(ItemEvent event) { Object o = event.getSource(); if (o == ports && event.getStateChange() == ItemEvent.SELECTED) { String port = (String) ports.getSelectedItem(); if (port.length() == 0) { // send("disconnect"); } else if (!port.equals(mySerial.getPortName()) && port.length() > 0) { // send("disconnect"); send("connect", port); } } if (o == reqFormat) { String newFormat = (String) reqFormat.getSelectedItem(); // changing our display and the Service's format try { rxFormatter = Codec.getDecoder(newFormat, myService); txFormatter = Codec.getDecoder(newFormat, myService); send("setFormat", newFormat); } catch (Exception e) { Logging.logError(e); } } } /** * publishRX displays the "interpreted" byte it is interpreted by the * Serial's service selected "format" * * FORMAT_DECIMEL is a 3 digit decimal in ascii FORMAT_RAW is interpreted as * 1 byte = 1 ascii char FORMAT_HEX is 2 digit asci hex * * @param data * @throws BadLocationException * @throws CodecException */ public final void publishRX(final Integer data) throws BadLocationException { ++rxCount; String formatted = rxFormatter.decode(data); rx.append(formatted); if (formatted != null && rx.getLineCount() > 50) { Document doc = rx.getDocument(); doc.remove(0, formatted.length()); } // rx.append(String.format("%s ", data)); /* * if (!mySerial.getDisplayFormat().equals(Serial.DISPLAY_RAW) && width * != null && rxCount % width == 0) { rx.append("\n"); } */ /* * FIXME FIXME FIXME THE CODEC SHOULD FORMAT !!!!! if (width != null && * rxCount % width == 0) { rx.append("\n"); } */ rxTotal.setText(String.format("%d", rxCount)); } public final void publishTX(final Integer data) { ++txCount; tx.append(txFormatter.decode(data)); /* * if (!mySerial.getDisplayFormat().equals(Serial.DISPLAY_RAW) && width * != null && txCount % width == 0) { tx.append("\n"); } */ /* * FIXME FIXME FIXME THE CODEC SHOULD FORMAT !!!!! if (width != null && * txCount % width == 0) { tx.append("\n"); } */ txTotal.setText(String.format("%d", txCount)); } public void setPortStatus() { ports.removeItemListener(myself); if (mySerial.isConnected()) { connectLight.setIcon(Util.getImageIcon("green.png")); log.info(String.format("displaying %s", mySerial.getPortName())); ports.setSelectedItem(mySerial.getPortName()); } else { connectLight.setIcon(Util.getImageIcon("red.png")); ports.setSelectedItem(""); } // zod ports.addItemListener(myself); } }