001package jmri.jmrix.pricom.pockettester;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.FlowLayout;
005import java.io.DataInputStream;
006import java.io.OutputStream;
007import java.util.Vector;
008import javax.swing.Action;
009import javax.swing.BoxLayout;
010import javax.swing.ButtonGroup;
011import javax.swing.JButton;
012import javax.swing.JLabel;
013import javax.swing.JPanel;
014import javax.swing.JRadioButton;
015import javax.swing.JSeparator;
016import purejavacomm.CommPortIdentifier;
017import purejavacomm.NoSuchPortException;
018import purejavacomm.PortInUseException;
019import purejavacomm.SerialPort;
020import purejavacomm.UnsupportedCommOperationException;
021
022/**
023 * Simple GUI for controlling the PRICOM Pocket Tester.
024 * <p>
025 * When opened, the user must first select a serial port and click "Start". The
026 * rest of the GUI then appears.
027 * <p>
028 * For more info on the product, see http://www.pricom.com
029 *
030 * @author Bob Jacobsen Copyright (C) 2001, 2002
031 */
032public class DataSource extends jmri.util.JmriJFrame {
033
034    static DataSource existingInstance;
035
036    /**
037     * Provide access to a defined DataSource object.
038     * <p>
039     * Note that this can be used to get the DataSource object once it's been
040     * created, even if it's not connected to the hardware yet.
041     *
042     * @return null until a DataSource has been created.
043     */
044    static public DataSource instance() {
045        return existingInstance;
046    }
047
048    static void setInstance(DataSource source) {
049        if (existingInstance != null) {
050            log.error("Setting instance after it has already been set");
051        } else {
052            existingInstance = source;
053        }
054    }
055
056    Vector<String> portNameVector = null;
057    SerialPort activeSerialPort = null;
058
059    JLabel version = new JLabel("");  // hold version label when returned
060
061    /**
062     * Populate the GUI.
063     *
064     * @since 1.7.7
065     */
066    @Override
067    public void initComponents() {
068        setTitle(Bundle.getMessage("TitleSource"));
069
070        // set layout manager
071        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
072
073        // load the port selection part
074        portBox.setToolTipText(Bundle.getMessage("TooltipSelectPort"));
075        portBox.setAlignmentX(JLabel.LEFT_ALIGNMENT);
076        Vector<String> v = getPortNames();
077        for (int i = 0; i < v.size(); i++) {
078            portBox.addItem(v.elementAt(i));
079        }
080        speedBox.setToolTipText(Bundle.getMessage("TooltipSelectBaud"));
081        speedBox.setAlignmentX(JLabel.LEFT_ALIGNMENT);
082        speedBox.setSelectedItem("115200");
083        openPortButton.setText(Bundle.getMessage("ButtonOpen"));
084        openPortButton.setToolTipText(Bundle.getMessage("TooltipOpen"));
085        openPortButton.addActionListener(new java.awt.event.ActionListener() {
086            @Override
087            public void actionPerformed(java.awt.event.ActionEvent evt) {
088                try {
089                    openPortButtonActionPerformed(evt);
090                    //} catch (jmri.jmrix.SerialConfigException ex) {
091                    //    log.error("Error while opening port.  Did you select the right one?\n"+ex);
092                } catch (java.lang.UnsatisfiedLinkError ex) {
093                    log.error("Error while opening port.  Did you select the right one?", ex);
094                }
095            }
096        });
097        getContentPane().add(new JSeparator());
098        JPanel p1 = new JPanel();
099        p1.setLayout(new FlowLayout());
100        p1.add(new JLabel(Bundle.getMessage("LabelSerialPort")));
101        p1.add(portBox);
102        p1.add(new JLabel(Bundle.getMessage("LabelSpeed")));
103        p1.add(speedBox);
104        p1.add(openPortButton);
105        getContentPane().add(p1);
106
107        setInstance(this);  // not done until init is basically complete
108
109        // Done, get ready to display
110        pack();
111    }
112
113    void addUserGui() {
114        // add user part of GUI
115        getContentPane().add(new JSeparator());
116        JPanel p2 = new JPanel();
117        p2.add(checkButton);
118        checkButton.addActionListener(new java.awt.event.ActionListener() {
119            @Override
120            public void actionPerformed(java.awt.event.ActionEvent evt) {
121                sendBytes(new byte[]{(byte) 'G'});
122                sendBytes(new byte[]{(byte) 'F'});
123            }
124        });
125
126        {
127            JPanel p = new JPanel();
128            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
129            ButtonGroup g = new ButtonGroup();
130            JRadioButton b;
131            b = new JRadioButton(Bundle.getMessage("ButtonShowAll"));
132            g.add(b);
133            p.add(b);
134            b.setSelected(true);
135            b.addActionListener(new java.awt.event.ActionListener() {
136                @Override
137                public void actionPerformed(java.awt.event.ActionEvent evt) {
138                    sendBytes(new byte[]{(byte) 'F'});
139                }
140            });
141            b = new JRadioButton(Bundle.getMessage("ButtonShowAcc"));
142            g.add(b);
143            p.add(b);
144            b.addActionListener(new java.awt.event.ActionListener() {
145                @Override
146                public void actionPerformed(java.awt.event.ActionEvent evt) {
147                    sendBytes(new byte[]{(byte) 'A'});
148                }
149            });
150            p2.add(p);
151            b = new JRadioButton(Bundle.getMessage("ButtonShowMobile"));
152            g.add(b);
153            p.add(b);
154            b.addActionListener(new java.awt.event.ActionListener() {
155                @Override
156                public void actionPerformed(java.awt.event.ActionEvent evt) {
157                    sendBytes(new byte[]{(byte) 'M'});
158                }
159            });
160            p2.add(p);
161        }  // end group controlling filtering
162
163        {
164            JButton b = new JButton(Bundle.getMessage("ButtonGetVersion"));
165            b.addActionListener(new java.awt.event.ActionListener() {
166                @Override
167                public void actionPerformed(java.awt.event.ActionEvent evt) {
168                    version.setText(Bundle.getMessage("LabelWaitVersion"));
169                    sendBytes(new byte[]{(byte) 'V'});
170                }
171            });
172            p2.add(b);
173        }
174
175        getContentPane().add(p2);
176
177        // space for version string
178        version = new JLabel(Bundle.getMessage("LabelNoVersion", Bundle.getMessage("ButtonGetVersion"))); // hold version label when returned
179        JPanel p3 = new JPanel();
180        p3.add(version);
181        getContentPane().add(p3);
182
183        getContentPane().add(new JSeparator());
184
185        JPanel p4 = new JPanel();
186        p4.setLayout(new BoxLayout(p4, BoxLayout.X_AXIS));
187        p4.add(new JLabel(Bundle.getMessage("LabelToOpen")));
188
189        {
190            MonitorAction a = new MonitorAction() {
191                @Override
192                public void connect(DataListener l) {
193                    DataSource.this.addListener(l);
194                }
195            };
196            JButton b = new JButton((String) a.getValue(Action.NAME));
197            b.addActionListener(a);
198            p4.add(b);
199        }
200
201        {
202            PacketTableAction p = new PacketTableAction() {
203                @Override
204                public void connect(DataListener l) {
205                    DataSource.this.addListener(l);
206                    if (l instanceof PacketTableFrame) {
207                        ((PacketTableFrame) l).setSource(DataSource.this);
208                    }
209                }
210            };
211            JButton b = new JButton((String) p.getValue(Action.NAME));
212            b.addActionListener(p);
213            p4.add(b);
214        }
215
216        {
217            StatusAction a = new StatusAction() {
218                @Override
219                public void connect(StatusFrame l) {
220                    DataSource.this.addListener(l);
221                    l.setSource(DataSource.this);
222                }
223            };
224            JButton b = new JButton((String) a.getValue(Action.NAME));
225            b.addActionListener(a);
226            p4.add(b);
227            getContentPane().add(p4);
228        }
229
230        // Done, get ready to display
231        pack();
232    }
233
234    JButton checkButton = new JButton(Bundle.getMessage("ButtonInit"));
235
236    /**
237     * Send output bytes, e.g. characters controlling operation, to the tester
238     * with small delays between the characters. This is used to reduce overrrun
239     * problems.
240     * @param bytes content to send
241     */
242    synchronized void sendBytes(byte[] bytes) {
243        try {
244            for (int i = 0; i < bytes.length - 1; i++) {
245                ostream.write(bytes[i]);
246                wait(3);
247            }
248            final byte endbyte = bytes[bytes.length - 1];
249            ostream.write(endbyte);
250        } catch (java.io.IOException e) {
251            log.error("Exception on output", e);
252        } catch (java.lang.InterruptedException e) {
253            Thread.currentThread().interrupt(); // retain if needed later
254            log.error("Interrupted output", e);
255        }
256    }
257
258    /**
259     * Open button has been pushed, create the actual display connection
260     * @param e Event driving this action
261     */
262    void openPortButtonActionPerformed(java.awt.event.ActionEvent e) {
263        log.info("Open button pushed");
264        // can't change this anymore
265        openPortButton.setEnabled(false);
266        portBox.setEnabled(false);
267        speedBox.setEnabled(false);
268        // Open the port
269        openPort((String) portBox.getSelectedItem(), "JMRI");
270        // start the reader
271        readerThread = new Thread(new Reader());
272        readerThread.start();
273        log.info("Open button processing complete");
274        addUserGui();
275    }
276
277    Thread readerThread;
278
279    protected javax.swing.JComboBox<String> portBox = new javax.swing.JComboBox<String>();
280    protected javax.swing.JComboBox<String> speedBox
281            = new javax.swing.JComboBox<String>(new String[]{"9600", "19200", "38400", "57600", "115200"});
282    protected javax.swing.JButton openPortButton = new javax.swing.JButton();
283
284    @SuppressWarnings("deprecation") // Thread.stop
285    @Override
286    public void dispose() {
287        if (readerThread != null) {
288            readerThread.stop();
289        }
290
291        // release port
292        if (activeSerialPort != null) {
293            activeSerialPort.close();
294        }
295        serialStream = null;
296        ostream = null;
297        activeSerialPort = null;
298        portNameVector = null;
299
300        // and clean up parent
301        super.dispose();
302    }
303
304    public Vector<String> getPortNames() {
305        return jmri.jmrix.AbstractSerialPortController.getActualPortNames();
306    }
307
308    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SR_NOT_CHECKED",
309                                        justification="this is for skip-chars while loop: no matter how many, we're skipping")
310    public String openPort(String portName, String appName) {
311        // open the port, check ability to set moderators
312        try {
313            // get and open the primary port
314            CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier(portName);
315            try {
316                activeSerialPort = (SerialPort) portID.open(appName, 2000);  // name of program, msec to wait
317            } catch (PortInUseException p) {
318                handlePortBusy(p, portName);
319                return "Port " + p + " in use already";
320            }
321
322            // try to set it for communication via SerialDriver
323            try {
324                // get selected speed
325                int speed = 115200;
326                speed = Integer.parseInt((String) speedBox.getSelectedItem());
327                // 8-bits, 1-stop, no parity
328                activeSerialPort.setSerialPortParams(speed, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
329            } catch (UnsupportedCommOperationException e) {
330                log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage());
331                return "Cannot set serial parameters on port " + portName + ": " + e.getMessage();
332            }
333
334            // NO hardware handshaking, but for consistancy, set the Modem Control Lines
335            // set RTS high, DTR high
336            activeSerialPort.setRTS(true); // not connected in some serial ports and adapters
337            activeSerialPort.setDTR(true); // pin 1 in DIN8; on main connector, this is DTR
338
339            // disable flow control; None is needed or used
340            activeSerialPort.setFlowControlMode(0);
341
342            // set timeout
343            log.debug("Serial timeout was observed as: {} {}", activeSerialPort.getReceiveTimeout(), activeSerialPort.isReceiveTimeoutEnabled());
344
345            // get and save stream
346            serialStream = new DataInputStream(activeSerialPort.getInputStream());
347            ostream = activeSerialPort.getOutputStream();
348
349            // start the DUMP
350            sendBytes(new byte[]{(byte) 'g'});
351            // purge contents, if any
352            int count = serialStream.available();
353            log.debug("input stream shows {} bytes available", count);
354            while (count > 0) {
355                serialStream.skip(count);
356                count = serialStream.available();
357            }
358
359            // report status?
360            if (log.isInfoEnabled()) {
361                log.info("{} port opened at {} baud, sees  DTR: {} RTS: {} DSR: {} CTS: {}  CD: {}", portName, activeSerialPort.getBaudRate(), activeSerialPort.isDTR(), activeSerialPort.isRTS(), activeSerialPort.isDSR(), activeSerialPort.isCTS(), activeSerialPort.isCD());
362            }
363
364        } catch (java.io.IOException ex) {
365            log.error("Unexpected I/O exception while opening port {}", portName, ex);
366            return "Unexpected error while opening port " + portName + ": " + ex;
367        } catch (NoSuchPortException ex) {
368            log.error("No such port while opening port {}", portName, ex);
369            return "Unexpected error while opening port " + portName + ": " + ex;
370        } catch (UnsupportedCommOperationException ex) {
371            log.error("Unexpected comm exception while opening port {}", portName, ex);
372            return "Unexpected error while opening port " + portName + ": " + ex;
373        }
374        return null; // indicates OK return
375    }
376
377    void handlePortBusy(PortInUseException p, String port) {
378        log.error("Port {} in use, cannot open", port, p);
379    }
380
381    DataInputStream serialStream = null;
382
383    @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC",
384            justification = "Class is no longer active, no hardware with which to test fix")
385    OutputStream ostream = null;
386
387    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataSource.class);
388
389    /**
390     * Internal class to handle the separate character-receive thread
391     *
392     */
393    class Reader implements Runnable {
394
395        /**
396         * Handle incoming characters. This is a permanent loop, looking for
397         * input messages in character form on the stream connected to the
398         * PortController via <code>connectPort</code>. Terminates with the
399         * input stream breaking out of the try block.
400         */
401        @Override
402        public void run() {
403            // have to limit verbosity!
404
405            while (true) {   // loop permanently, stream close will exit via exception
406                try {
407                    handleIncomingData();
408                } catch (java.io.IOException e) {
409                    log.warn("run: Exception: {}", e.toString());
410                }
411            }
412        }
413
414        static final int maxMsg = 200;
415        StringBuffer msg;
416        String msgString;
417
418        void handleIncomingData() throws java.io.IOException {
419            // we sit in this until the message is complete, relying on
420            // threading to let other stuff happen
421
422            // Create output message
423            msg = new StringBuffer(maxMsg);
424            // message exists, now fill it
425            int i;
426            for (i = 0; i < maxMsg; i++) {
427                char char1 = (char) serialStream.readByte();
428                if (char1 == 10) {  // 10 is the LF at the end; done this
429                    // way to be coding-independent
430                    break;
431                }
432                // Strip off the CR and LF
433                if (char1 != 13) {
434                    msg.append(char1);
435                }
436            }
437
438            // create the String to display (as String has .equals)
439            msg.append("\n");
440            msgString = msg.toString();
441
442            // return a notification via the queue to ensure end
443            Runnable r = new Runnable() {
444
445                // retain a copy of the message at startup
446                String msgForLater = msgString;
447
448                @Override
449                public void run() {
450                    nextLine(msgForLater);
451                }
452            };
453            javax.swing.SwingUtilities.invokeLater(r);
454        }
455
456    } // end class Reader
457
458    // data members to hold contact with the listeners
459    final private Vector<DataListener> listeners = new Vector<DataListener>();
460
461    public synchronized void addListener(DataListener l) {
462        // add only if not already registered
463        if (!listeners.contains(l)) {
464            listeners.addElement(l);
465        }
466    }
467
468    public synchronized void removeListener(DataListener l) {
469        if (listeners.contains(l)) {
470            listeners.removeElement(l);
471        }
472    }
473
474    /**
475     * Handle a new line from the device.
476     * <ul>
477     * <li>Check for version number and display
478     * <li>Trigger the notification of all listeners.
479     * </ul>
480     * <p>
481     * This needs to execute on the Swing GUI thread.
482     *
483     * @param s The new message to distribute
484     */
485    protected void nextLine(String s) {
486        // Check for version string
487        if (s.startsWith("PRICOM Design DCC")) {
488            // save as version string & suppress
489            version.setText(s);
490            return;
491        }
492        // Distribute the result
493        // make a copy of the listener vector so synchronized not needed for transmit
494        Vector<DataListener> v;
495        synchronized (this) {
496            v = new Vector<DataListener>(listeners);
497        }
498        // forward to all listeners
499        int cnt = v.size();
500        for (int i = 0; i < cnt; i++) {
501            DataListener client = v.elementAt(i);
502            client.asciiFormattedMessage(s);
503        }
504    }
505
506}