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}