001package jmri.jmrix.ncemonitor;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.io.DataInputStream;
008import java.io.IOException;
009import java.io.OutputStream;
010import java.util.Vector;
011
012import javax.swing.BoxLayout;
013import javax.swing.ButtonGroup;
014import javax.swing.JCheckBox;
015import javax.swing.JComboBox;
016import javax.swing.JLabel;
017import javax.swing.JPanel;
018import javax.swing.JRadioButton;
019import javax.swing.JScrollPane;
020import javax.swing.JSeparator;
021
022import jmri.jmrix.AbstractSerialPortController;
023import jmri.jmrix.AbstractSerialPortController.SerialPort;
024import jmri.jmrix.nce.NceSystemConnectionMemo;
025import jmri.jmrix.nce.swing.NcePanelInterface;
026
027/**
028 * Simple GUI for access to an NCE monitor card
029 * <p>
030 * When opened, the user must first select a serial port and click "Start". The
031 * rest of the GUI then appears.
032 *
033 * @author Ken Cameron Copyright (C) 2010 derived from -
034 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2023
035 * @author Ken Cameron Copyright (C) 2023
036 */
037@SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "serialStream is access from separate thread, and this class isn't used much")
038public class NcePacketMonitorPanel extends jmri.jmrix.AbstractMonPane implements NcePanelInterface {
039
040    Vector<String> portNameVector = null;
041    SerialPort activeSerialPort = null;
042    NceSystemConnectionMemo memo = null;
043
044    protected JCheckBox dupFilterCheckBox = new JCheckBox(Bundle.getMessage("DupFilterCheckBoxLabel"));
045    protected JComboBox<String> portBox = new javax.swing.JComboBox<String>();
046    protected javax.swing.JButton openButton = new javax.swing.JButton(Bundle.getMessage("OpenButtonLabel"));
047    protected javax.swing.JButton closePortButton = new javax.swing.JButton(Bundle.getMessage("CloseButtonLabel"));
048    protected JRadioButton verboseButton = new JRadioButton(Bundle.getMessage("VerboseButtonLabel"));
049    protected JRadioButton origHex0Button = new JRadioButton(Bundle.getMessage("OrigHex0Label"));
050    protected JRadioButton origHex1Button = new JRadioButton(Bundle.getMessage("OrigHex1Label"));
051    protected JRadioButton origHex2Button = new JRadioButton(Bundle.getMessage("OrigHex2Label"));
052    protected JRadioButton origHex3Button = new JRadioButton(Bundle.getMessage("OrigHex3Label"));
053    protected JRadioButton origHex4Button = new JRadioButton(Bundle.getMessage("OrigHex4Label"));
054    protected JRadioButton origHex5Button = new JRadioButton(Bundle.getMessage("OrigHex5Label"));
055    protected JRadioButton newHex0Button = new JRadioButton(Bundle.getMessage("NewHex0Label"));
056    protected JRadioButton newHex1Button = new JRadioButton(Bundle.getMessage("NewHex1Label"));
057    protected JRadioButton accOnButton = new JRadioButton(Bundle.getMessage("AccOnLabel"));
058    protected JRadioButton idleOnButton = new JRadioButton(Bundle.getMessage("IdleOnLabel"));
059    protected JRadioButton locoOnButton = new JRadioButton(Bundle.getMessage("LocoOnLabel"));
060    protected JRadioButton resetOnButton = new JRadioButton(Bundle.getMessage("ResetOnLabel"));
061    protected JRadioButton signalOnButton = new JRadioButton(Bundle.getMessage("SignalOnLabel"));
062    protected JRadioButton accSingleButton = new JRadioButton(Bundle.getMessage("AccSingleLabel"));
063    protected JRadioButton accPairedButton = new JRadioButton(Bundle.getMessage("AccPairedLabel"));
064
065    protected JComboBox<String> modelBox = new JComboBox<>();
066    protected JLabel modelBoxLabel;
067    private String[] validModelNames = new String[]{Bundle.getMessage("PacketAnalyzer"), Bundle.getMessage("DccMeter/Analyzer")};
068    private final static int MODELORIG = 0;
069    private final static int MODELNEW = 1;
070    private int[] validModelValues = new int[]{MODELORIG, MODELNEW};
071    private int[] modelBaudRates = new int[]{38400, 115200};
072    // For old model, Doc says 7 bits, but 8 seems needed, new calls for 115,200 n 8 1
073    private int[] modelBitValues = new int[] {8, 8};
074    private int[] modelStopValues = new int[] {SerialPort.ONE_STOP_BIT, SerialPort.ONE_STOP_BIT};
075    private int[] modelParityValues = new int[] {SerialPort.NO_PARITY, SerialPort.NO_PARITY};
076
077    public NcePacketMonitorPanel() {
078        super();
079    }
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    public void init() {
086    }
087
088    /**
089     * {@inheritDoc}
090     */
091    @Override
092    public void initContext(Object context) {
093        if (context instanceof NceSystemConnectionMemo) {
094            initComponents((NceSystemConnectionMemo) context);
095        }
096    }
097
098    /**
099     * {@inheritDoc}
100     */
101    @Override
102    public String getHelpTarget() {
103        return "package.jmri.jmrix.nce.analyzer.NcePacketMonitorFrame";
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    public String getTitle() {
111        StringBuilder x = new StringBuilder();
112        if (memo != null) {
113            x.append(memo.getUserName());
114        } else {
115            x.append("NCE_");
116        }
117        x.append(": ");
118        x.append(Bundle.getMessage("Title"));
119        return x.toString();
120    }
121
122    /**
123     * {@inheritDoc}
124     */
125    @Override
126    public void initComponents(NceSystemConnectionMemo m) {
127        this.memo = m;
128
129        // populate the GUI, invoked as part of startup
130        enableDisableWhenOpen(false);
131        // load the port selection part
132        portBox.setToolTipText(Bundle.getMessage("PortBoxToolTip"));
133        portBox.setAlignmentX(JLabel.LEFT_ALIGNMENT);
134        Vector<String> v = getPortNames();
135        for (int i = 0; i < v.size(); i++) {
136            portBox.addItem(v.elementAt(i));
137        }
138        // offer model choice
139        modelBox.setToolTipText(Bundle.getMessage("ModelBoxToolTip"));
140        modelBox.setAlignmentX(LEFT_ALIGNMENT);
141        for (int i = 0; i < validModelNames.length; i++) {
142            modelBox.addItem(validModelNames[i]);
143        }
144        openButton.setToolTipText(Bundle.getMessage("OpenButtonToolTip"));
145        openButton.addActionListener(new java.awt.event.ActionListener() {
146            @Override
147            public void actionPerformed(java.awt.event.ActionEvent evt) {
148                try {
149                    openPortButtonActionPerformed(evt);
150                } catch (java.lang.UnsatisfiedLinkError ex) {
151                    log.error("Error while opening port.  Did you select the right one?\nException: ", ex);
152                }
153            }
154        });
155        closePortButton.setToolTipText(Bundle.getMessage("CloseButtonToolTip"));
156        closePortButton.addActionListener(new java.awt.event.ActionListener() {
157            @Override
158            public void actionPerformed(java.awt.event.ActionEvent evt) {
159                try {
160                    closePortButtonActionPerformed();
161                } catch (java.lang.UnsatisfiedLinkError ex) {
162                    log.error("Error while closing port.  Did you select the right one?\\nException: ", ex);
163                }
164            }
165        });
166        {
167            JSeparator js = new JSeparator();
168            js.setMaximumSize(new Dimension(10000, 10));
169            add(js);
170        }
171        {
172            JPanel p1 = new JPanel();
173            p1.setLayout(new FlowLayout());
174            p1.add(new JLabel(Bundle.getMessage("SerialPortLabel")));
175            p1.add(portBox);
176            p1.add(new JLabel(Bundle.getMessage("ModelBoxLabel")));
177            p1.add(modelBox);
178            p1.add(openButton);
179            p1.add(closePortButton);
180            //p1.setMaximumSize(p1.getPreferredSize());
181            add(p1);
182        }
183
184        // add user part of GUI
185        {
186            JSeparator js = new JSeparator();
187            js.setMaximumSize(new Dimension(10000, 10));
188            add(js);
189        }
190        JPanel p2 = new JPanel();
191        JPanel p2A = new JPanel();
192        p2A.setLayout(new BoxLayout(p2A, BoxLayout.Y_AXIS));
193        JPanel p2B = new JPanel();
194        JPanel p2C = new JPanel();
195        JPanel p2D = new JPanel();
196        ButtonGroup gD = new ButtonGroup();
197        {   // begin dup group
198            JPanel p = new JPanel();
199            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
200            dupFilterCheckBox.setToolTipText(Bundle.getMessage("DupFilterCheckBoxToolTip"));
201            p.add(dupFilterCheckBox);
202            p2.add(p);
203        }   // end dup group
204
205        {   // begin verbose group
206            JPanel p = new JPanel();
207            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
208            verboseButton.setToolTipText(Bundle.getMessage("VerboseButtonToolTip"));
209            gD.add(verboseButton);
210            p.add(verboseButton);
211            verboseButton.addActionListener(new java.awt.event.ActionListener() {
212                @Override
213                public void actionPerformed(java.awt.event.ActionEvent evt) {
214                    sendBytes(new byte[]{(byte) 'V'});
215                }
216            });
217            p2A.add(p);
218        }   // end verbose group
219
220        {   // begin old hex group
221            JPanel p = new JPanel();
222            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
223            origHex0Button.setToolTipText(Bundle.getMessage("OrigHex0ButtonToolTip"));
224            gD.add(origHex0Button);
225            p.add(origHex0Button);
226            origHex0Button.addActionListener(new java.awt.event.ActionListener() {
227                @Override
228                public void actionPerformed(java.awt.event.ActionEvent evt) {
229                    sendBytes(new byte[]{(byte) 'H', (byte) '0'});
230                }
231            });
232            p2B.add(p);
233            origHex1Button.setToolTipText(Bundle.getMessage("OrigHex1ButtonToolTip"));
234            gD.add(origHex1Button);
235            p.add(origHex1Button);
236            origHex1Button.addActionListener(new java.awt.event.ActionListener() {
237                @Override
238                public void actionPerformed(java.awt.event.ActionEvent evt) {
239                    sendBytes(new byte[]{(byte) 'H', (byte) '1'});
240                }
241            });
242            p2B.add(p);
243            origHex2Button.setToolTipText(Bundle.getMessage("OrigHex2ButtonToolTip"));
244            gD.add(origHex2Button);
245            p.add(origHex2Button);
246            origHex2Button.addActionListener(new java.awt.event.ActionListener() {
247                @Override
248                public void actionPerformed(java.awt.event.ActionEvent evt) {
249                    sendBytes(new byte[]{(byte) 'H', (byte) '2'});
250                }
251            });
252            p2.add(p);
253            origHex3Button.setToolTipText(Bundle.getMessage("OrigHex3ButtonToolTip"));
254            gD.add(origHex3Button);
255            p.add(origHex3Button);
256            origHex3Button.addActionListener(new java.awt.event.ActionListener() {
257                @Override
258                public void actionPerformed(java.awt.event.ActionEvent evt) {
259                    sendBytes(new byte[]{(byte) 'H', (byte) '3'});
260                }
261            });
262            p2B.add(p);
263            origHex4Button.setToolTipText(Bundle.getMessage("OrigHex4ButtonToolTip"));
264            gD.add(origHex4Button);
265            p.add(origHex4Button);
266            origHex4Button.addActionListener(new java.awt.event.ActionListener() {
267                @Override
268                public void actionPerformed(java.awt.event.ActionEvent evt) {
269                    sendBytes(new byte[]{(byte) 'H', (byte) '4'});
270                }
271            });
272            p2.add(p);
273            origHex5Button.setToolTipText(Bundle.getMessage("OrigHex5ButtonToolTip"));
274            gD.add(origHex5Button);
275            p.add(origHex5Button);
276            origHex5Button.addActionListener(new java.awt.event.ActionListener() {
277                @Override
278                public void actionPerformed(java.awt.event.ActionEvent evt) {
279                    sendBytes(new byte[]{(byte) 'H', (byte) '5'});
280                }
281            });
282            p2B.add(p);
283        }  // end old hex group
284
285        {   // begin new hex group
286            JPanel p = new JPanel();
287            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
288            newHex0Button.setToolTipText(Bundle.getMessage("NewHex0ButtonToolTip"));
289            gD.add(newHex0Button);
290            p.add(newHex0Button);
291            newHex0Button.addActionListener(new java.awt.event.ActionListener() {
292                @Override
293                public void actionPerformed(java.awt.event.ActionEvent evt) {
294                    sendBytes(new byte[]{(byte) 'H', (byte) '0'});
295                }
296            });
297            p2C.add(p);
298            newHex1Button.setToolTipText(Bundle.getMessage("NewHex1ButtonToolTip"));
299            gD.add(newHex1Button);
300            p.add(newHex1Button);
301            newHex1Button.addActionListener(new java.awt.event.ActionListener() {
302                @Override
303                public void actionPerformed(java.awt.event.ActionEvent evt) {
304                    sendBytes(new byte[]{(byte) 'H', (byte) '1'});
305                }
306            });
307            p2C.add(p);
308        }  // end new hex group
309        p2D.setLayout(new BoxLayout(p2D, BoxLayout.X_AXIS));
310        p2D.add(p2B);
311        p2D.add(p2C);
312        p2A.add(p2D);
313        p2.add(p2A);
314
315        { // start on
316            JPanel p = new JPanel();
317            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
318            accOnButton.setToolTipText(Bundle.getMessage("AccOnButtonToolTip"));
319            p.add(accOnButton);
320            accOnButton.addActionListener(new java.awt.event.ActionListener() {
321                @Override
322                public void actionPerformed(java.awt.event.ActionEvent evt) {
323                    if (accOnButton.isSelected()) {
324                        sendBytes(new byte[]{(byte) 'A', (byte) '+'});
325                    } else {
326                        sendBytes(new byte[]{(byte) 'A', (byte) '-'});
327                    }
328                }
329            });
330            idleOnButton.setToolTipText(Bundle.getMessage("IdleOnButtonToolTip"));
331            p.add(idleOnButton);
332            idleOnButton.addActionListener(new java.awt.event.ActionListener() {
333                @Override
334                public void actionPerformed(java.awt.event.ActionEvent evt) {
335                    if (idleOnButton.isSelected()) {
336                        sendBytes(new byte[]{(byte) 'I', (byte) '+'});
337                    } else {
338                        sendBytes(new byte[]{(byte) 'I', (byte) '-'});
339                    }
340                }
341            });
342            locoOnButton.setToolTipText(Bundle.getMessage("LocoOnButtonToolTip"));
343            p.add(locoOnButton);
344            locoOnButton.addActionListener(new java.awt.event.ActionListener() {
345                @Override
346                public void actionPerformed(java.awt.event.ActionEvent evt) {
347                    if (locoOnButton.isSelected()) {
348                        sendBytes(new byte[]{(byte) 'L', (byte) '+'});
349                    } else {
350                        sendBytes(new byte[]{(byte) 'L', (byte) '-'});
351                    }
352                }
353            });
354            resetOnButton.setToolTipText(Bundle.getMessage("ResetOnButtonToolTip"));
355            p.add(resetOnButton);
356            resetOnButton.addActionListener(new java.awt.event.ActionListener() {
357                @Override
358                public void actionPerformed(java.awt.event.ActionEvent evt) {
359                    if (resetOnButton.isSelected()) {
360                        sendBytes(new byte[]{(byte) 'R', (byte) '+'});
361                    } else {
362                        sendBytes(new byte[]{(byte) 'R', (byte) '-'});
363                    }
364                }
365            });
366            signalOnButton.setToolTipText(Bundle.getMessage("SignalOnButtonToolTip"));
367            p.add(signalOnButton);
368            signalOnButton.addActionListener(new java.awt.event.ActionListener() {
369                @Override
370                public void actionPerformed(java.awt.event.ActionEvent evt) {
371                    if (signalOnButton.isSelected()) {
372                        sendBytes(new byte[]{(byte) 'S', (byte) '+'});
373                    } else {
374                        sendBytes(new byte[]{(byte) 'S', (byte) '-'});
375                    }
376                }
377            });
378            p2.add(p);
379        }  // end on
380
381        { // Monitor command acc single/double
382            JPanel p = new JPanel();
383            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
384            JLabel t = new JLabel(Bundle.getMessage("MonitorCmdLabel"));
385            p.add(t);
386            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
387            ButtonGroup gA = new ButtonGroup();
388            accSingleButton.setToolTipText(Bundle.getMessage("AccSingleButtonToolTip"));
389            gA.add(accSingleButton);
390            p.add(accSingleButton);
391            accSingleButton.addActionListener(new java.awt.event.ActionListener() {
392                @Override
393                public void actionPerformed(java.awt.event.ActionEvent evt) {
394                    sendBytes(new byte[]{(byte) 'A', (byte) 'S'});
395                }
396            });
397            accPairedButton.setToolTipText(Bundle.getMessage("AccPairedButtonToolTip"));
398            gA.add(accPairedButton);
399            p.add(accPairedButton);
400            accPairedButton.addActionListener(new java.awt.event.ActionListener() {
401                @Override
402                public void actionPerformed(java.awt.event.ActionEvent evt) {
403                    sendBytes(new byte[]{(byte) 'A', (byte) 'P'});
404                }
405            });
406            p2.add(p);
407        }  // end acc single/double
408
409        p2.setMaximumSize(p2.getPreferredSize());
410        JScrollPane ps = new JScrollPane(p2);
411        ps.setMaximumSize(ps.getPreferredSize());
412        ps.setVisible(true);
413        add(ps);
414    }
415
416    /**
417     * Sends stream of bytes to the command station
418     *
419     * @param bytes  array of bytes to send
420     */
421    synchronized void sendBytes(byte[] bytes) {
422        try {
423            // only attempt to send data if output stream is not null (i.e. it
424            // was opened successfully)
425            if (ostream == null) {
426                throw new IOException(
427                        "Unable to send data to command station: output stream is null");
428            } else {
429                for (int i = 0; i < bytes.length; i++) {
430                    ostream.write(bytes[i]);
431                    wait(3);
432                }
433                final byte endbyte = 13;
434                ostream.write(endbyte);
435            }
436        } catch (IOException e) {
437            log.error("Exception on output: ", e);
438        } catch (InterruptedException e) {
439            Thread.currentThread().interrupt(); // retain if needed later
440            log.error("Interrupted output: ", e);
441        }
442    }
443
444    /**
445     * Enable/Disable options depending on port open/closed status
446     * @param isOpen enables/disables buttons/checkbox when connection is open/closed
447     */
448    void enableDisableWhenOpen(boolean isOpen) {
449        openButton.setEnabled(!isOpen);
450        closePortButton.setEnabled(isOpen);
451        portBox.setEnabled(!isOpen);
452        modelBox.setEnabled(!isOpen);
453        verboseButton.setEnabled(isOpen);
454        if (!isOpen || (modelBox.getSelectedIndex() == MODELORIG)) {
455            origHex0Button.setEnabled(isOpen);
456            origHex1Button.setEnabled(isOpen);
457            origHex2Button.setEnabled(isOpen);
458            origHex3Button.setEnabled(isOpen);
459            origHex4Button.setEnabled(isOpen);
460            origHex5Button.setEnabled(isOpen);
461        }
462        if (!isOpen || (modelBox.getSelectedIndex() == MODELNEW)) {
463            newHex0Button.setEnabled(isOpen);
464            newHex1Button.setEnabled(isOpen);
465        }
466        accOnButton.setEnabled(isOpen);
467        idleOnButton.setEnabled(isOpen);
468        locoOnButton.setEnabled(isOpen);
469        resetOnButton.setEnabled(isOpen);
470        signalOnButton.setEnabled(isOpen);
471        accSingleButton.setEnabled(isOpen);
472        accPairedButton.setEnabled(isOpen);
473    }
474
475    /**
476     * Open button has been pushed, create the actual display connection
477     * @param e open button event
478     */
479    void openPortButtonActionPerformed(java.awt.event.ActionEvent e) {
480        //log.info("Open button pushed");
481        // can't change this anymore
482        String portName = (String) portBox.getSelectedItem();
483        int modelValue = validModelValues[modelBox.getSelectedIndex()];
484        int numDataBits = modelBitValues[modelValue];
485        int numStopBits = modelStopValues[modelValue];
486        int parity = modelParityValues[modelValue];
487        int baudrate = modelBaudRates[modelValue];
488        activeSerialPort = AbstractSerialPortController.activatePort(
489                null, portName, log, numStopBits, AbstractSerialPortController.Parity.getParity(parity));
490
491        activeSerialPort.setNumDataBits(numDataBits);
492        activeSerialPort.setBaudRate(baudrate);
493
494        // set RTS high, DTR high
495        activeSerialPort.setRTS(); // not connected in some serial ports and adapters
496        activeSerialPort.setDTR(); // pin 1 in DIN8; on main connector, this is DTR
497
498        // get and save stream
499        serialStream = new DataInputStream(activeSerialPort.getInputStream());
500        ostream = activeSerialPort.getOutputStream();
501
502        // report status?
503        if (log.isInfoEnabled()) {
504            log.info("Port {} {} opened at {} baud, sees DTR: {} RTS: {} DSR: {} CTS: {} DCD: {}",
505                    portName, activeSerialPort.getDescriptivePortName(),
506                    activeSerialPort.getBaudRate(), activeSerialPort.getDTR(),
507                    activeSerialPort.getRTS(), activeSerialPort.getDSR(), activeSerialPort.getCTS(),
508                    activeSerialPort.getDCD());
509        }
510
511        // start the reader
512        readerThread = new Thread(new Reader());
513        readerThread.start();
514        readerThread.setName("NCE Packet Monitor");
515        // enable buttons
516        enableDisableWhenOpen(true);
517        //log.info("Open button processing complete");
518    }
519
520    /**
521     * Open button has been pushed, create the actual display connection
522     */
523    void closePortButtonActionPerformed() {
524        //log.info("Close button pushed");
525        if (readerThread != null) {
526            stopThread(readerThread);
527        }
528
529        // release port
530        if (activeSerialPort != null) {
531            activeSerialPort.closePort();
532            log.info("{} port closed", portBox.getSelectedItem());
533        }
534        serialStream = null;
535        ostream = null;
536        activeSerialPort = null;
537        portNameVector = null;
538        // enable buttons
539        enableDisableWhenOpen(false);
540    }
541
542    Thread readerThread;
543
544    /*
545     * tell the reader thread to close down
546     */
547    void stopThread(Thread t) {
548        t.interrupt();
549    }
550
551    @Override
552    public synchronized void dispose() {
553        // stop operations here. This is a deprecated method, but OK for us.
554        closePortButtonActionPerformed();
555
556        // and clean up parent
557        super.dispose();
558    }
559
560    public Vector<String> getPortNames() {
561        return jmri.jmrix.AbstractSerialPortController.getActualPortNames();
562    }
563
564    DataInputStream serialStream = null;
565    OutputStream ostream = null;
566
567    /**
568     * Internal class to handle the separate character-receive thread
569     *
570     */
571    class Reader implements Runnable {
572
573        /**
574         * Handle incoming characters. This is a permanent loop, looking for
575         * input messages in character form on the stream connected to the
576         * PortController via <code>connectPort</code>. Terminates with the
577         * input stream breaking out of the try block.
578         */
579        @Override
580        public void run() {
581            // have to limit verbosity!
582
583            while (true) {   // loop permanently, stream close will exit via exception
584                try {
585                    handleIncomingData();
586                } catch (java.io.EOFException e) {
587                    log.info("{} thread ending, port closed", Thread.currentThread().getName());
588                    return;
589                } catch (java.io.IOException e) {
590                    log.warn("{} thread ending: Exception: {}", Thread.currentThread().getName(), e.toString());
591                    return;
592                }
593            }
594        }
595
596        static final int maxMsg = 80;
597        StringBuffer msg;
598        private int duplicates = 0;
599        String msgString;
600        String matchString = "";
601
602        void handleIncomingData() throws java.io.IOException {
603            // we sit in this until the message is complete, relying on
604            // threading to let other stuff happen
605
606            // Create output message
607            msg = new StringBuffer(maxMsg);
608            // message exists, now fill it
609            int i;
610            for (i = 0; i < maxMsg; i++) {
611                char char1 = (char) serialStream.readByte();
612                if (char1 == 13) {  // 13 is the CR at the end; done this
613                    // way to be coding-independent
614                    break;
615                }
616                msg.append(char1);
617            }
618
619            // create the String to display (as String has .equals)
620            msgString = msg.toString();
621
622            // is this a duplicate?
623            if (msgString.equals(matchString) && dupFilterCheckBox.isSelected()) {
624                // yes, keep count
625                duplicates++;
626            } else {
627                // no, message is complete, dispatch it!!
628                if (!msgString.equals(matchString) && dupFilterCheckBox.isSelected() && (duplicates > 0)) {
629                    // prepend the duplicate info
630                    String dupString = matchString + " [" + duplicates + "]\n";
631                    // return a notification via the queue to ensure end
632                    Runnable r = new Runnable() {
633                        @Override
634                        public void run() {
635                            nextLine(dupString, "");
636                        }
637                    };
638                    javax.swing.SwingUtilities.invokeLater(r);
639                }
640                duplicates = 0;
641                matchString = msgString;
642                msgString = msgString + "\n";
643                // return a notification via the queue to ensure end
644                Runnable r = new Runnable() {
645                    @Override
646                    public void run() {
647                        nextLine(msgString, "");
648                    }
649                };
650                javax.swing.SwingUtilities.invokeLater(r);
651            }
652        }
653
654    } // end class Reader
655
656    /**
657     * Nested class to create one of these using old-style defaults
658     */
659    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
660
661        public Default() {
662            super("Open NCE DCC Packet Analyzer",
663                    new jmri.util.swing.sdi.JmriJFrameInterface(),
664                    NcePacketMonitorPanel.class.getName(),
665                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
666        }
667    }
668
669    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NcePacketMonitorPanel.class);
670}