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