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