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