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}