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