001package jmri.jmrix.cmri.serial.diagnostic;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.Container;
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import javax.swing.BorderFactory;
010import javax.swing.BoxLayout;
011import javax.swing.JComboBox;
012import javax.swing.JLabel;
013import javax.swing.JPanel;
014import javax.swing.Timer;
015import javax.swing.border.Border;
016import jmri.util.StringUtil;
017
018import jmri.jmrix.cmri.CMRISystemConnectionMemo;
019import jmri.jmrix.cmri.serial.SerialMessage;
020import jmri.jmrix.cmri.serial.SerialNode;
021import jmri.jmrix.cmri.serial.SerialReply;
022import jmri.jmrix.cmri.serial.SerialTrafficController;
023
024/**
025 * Frame for running CMRI diagnostics
026 *
027 * @author Dave Duchamp Copyright (C) 2004
028 * @author Chuck Catania Copyright (C) 2018
029 */
030public class DiagnosticFrame extends jmri.util.JmriJFrame implements jmri.jmrix.cmri.serial.SerialListener {
031    protected int numTestNodes = 0;
032    protected SerialNode[] testNodes = new SerialNode[128];  // Node control blocks
033    protected int[] testNodeAddresses = new int[128];        // ua's of loaded nodes
034    
035    protected SerialNode testNode = null;                    // current node under test
036    public int testNodeAddr = 0;                             // Address (ua) of selected Node
037    protected String testNodeID = "x";                       // text address of selected Node
038    protected int testNodeType = 0;                          // Test node type e.g SMINI
039
040    JComboBox<String> nodeSelBox = new JComboBox<>();
041    JComboBox<String> testSelectBox = new JComboBox<>();
042
043    // member declarations
044    public static final int testType_Outputs    = 0,       // Write bit pattern to ports
045                            testType_Wraparound = 1,       // Write bit pattern to port, read and compare bit pattern. Needs loopback cable
046                            testType_SendCommand= 2,       // Poll node to check for presence, read inputs
047                            testType_WriteBytes = 3;       // Transmit output byte pattern
048    
049    protected int selTestType = testType_Outputs;    // Current test suite
050    protected boolean outTest = true;
051    protected boolean wrapTest = false;
052    protected boolean isSMINI = false;
053    protected boolean isUSIC_SUSIC = true;
054    protected boolean isCPNODE = false;
055    protected boolean isESP32NODE = false;
056    // Here add other node types
057    protected int numOutputCards = 2;
058    protected int numInputCards = 1;
059    protected int numCards = 3;
060    protected int numIOXInputCards = 0;
061    protected int numIOXOutputCards= 0;
062
063//    protected int ua = 0;               // node address
064//    protected SerialNode node;
065    protected int outCardNum = 0;
066    protected int obsDelay = 500;
067    protected int inCardNum = 2;
068    protected int filterDelay = 0;
069    // Test running variables
070    protected boolean testRunning = false;
071    protected boolean testSuspended = false;  // true when Wraparound is suspended by error
072    protected byte[] outBytes = new byte[256];
073    protected int curOutByte = 0;       // current output byte in output test
074    protected int curOutBit = 0;        // current on bit in current output byte in output test
075    protected short curOutValue = 0;    // current ofoutput byte in wraparound test
076    protected int nOutBytes = 6;        // number of output bytes for all cards of this node
077    protected int begOutByte = 0;       // numbering from zero, subscript in outBytes
078    protected int endOutByte = 2;
079    protected int totalOutBytes= 0;
080    protected int portsPerCard = 0;
081    protected byte[] inBytes = new byte[256];
082    protected byte[] wrapBytes = new byte[4];
083    protected int nInBytes = 3;         // number of input bytes for all cards of this node
084    protected int begInByte = 0;        // numbering from zero, subscript in inBytes
085    protected int replyCount= 0;        // number of bytes received from a poll
086
087    @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "unsync access only during initialization")
088    protected int endInByte = 2;
089
090    protected int numErrors = 0;
091    protected int numIterations = 0;
092    protected javax.swing.Timer outTimer;
093    protected javax.swing.Timer wrapTimer;
094    protected javax.swing.Timer pollTimer;
095
096    @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "unsync access only during initialization")
097    protected boolean waitingOnInput = false;
098    protected boolean waitingResponse = false;
099
100    protected boolean needInputTest = false;
101    protected int count = 20;
102    int debugCount = 0;
103    javax.swing.ButtonGroup testGroup = new javax.swing.ButtonGroup();
104    javax.swing.JCheckBox invertOutButton = new javax.swing.JCheckBox(Bundle.getMessage("ButtonInvert"), false);
105    javax.swing.JCheckBox invertWrapButton = new javax.swing.JCheckBox(Bundle.getMessage("ButtonInvert"), false);
106    javax.swing.JCheckBox invertWriteButton = new javax.swing.JCheckBox(Bundle.getMessage("ButtonInvert"), false);
107
108    javax.swing.JButton initButton = new javax.swing.JButton(Bundle.getMessage("ButtonInitializeNode"));
109    javax.swing.JButton pollButton = new javax.swing.JButton(Bundle.getMessage("ButtonPollNode"));
110    javax.swing.JButton writeButton = new javax.swing.JButton(Bundle.getMessage("ButtonWriteBytes"));
111    javax.swing.JButton haltPollButton = new javax.swing.JButton("Halt Polling" );
112
113    javax.swing.JTextField uaAddrField = new javax.swing.JTextField(3);
114    javax.swing.JTextField outCardField = new javax.swing.JTextField(3);
115    javax.swing.JTextField inCardField = new javax.swing.JTextField(3);
116    javax.swing.JTextField obsDelayField = new javax.swing.JTextField(5);
117    javax.swing.JTextField filterDelayField = new javax.swing.JTextField(5);
118    javax.swing.JTextField writeCardField = new javax.swing.JTextField(3);
119    javax.swing.JTextField writeBytesField = new javax.swing.JTextField(9);
120
121    javax.swing.JButton runButton = new javax.swing.JButton(Bundle.getMessage("ButtonRun"));
122    javax.swing.JButton stopButton = new javax.swing.JButton(Bundle.getMessage("ButtonStop"));
123    javax.swing.JButton continueButton = new javax.swing.JButton(Bundle.getMessage("ButtonContinue"));
124
125    javax.swing.JLabel nodeText1 = new javax.swing.JLabel();
126    javax.swing.JLabel nodeText2 = new javax.swing.JLabel();
127    javax.swing.JLabel testReqEquip = new javax.swing.JLabel(Bundle.getMessage("NeededEquipmentTitle"));
128    javax.swing.JLabel testEquip = new javax.swing.JLabel();
129    javax.swing.JLabel nodeReplyLabel = new javax.swing.JLabel(Bundle.getMessage("NodeReplyLabel"));
130    javax.swing.JLabel nodeReplyText = new javax.swing.JLabel();
131    javax.swing.JLabel writeCardLabel = new javax.swing.JLabel("Out Card:");
132    javax.swing.JLabel writeBytesLabel = new javax.swing.JLabel("Output Bytes (Hex):");
133   
134    javax.swing.JLabel statusText1 = new javax.swing.JLabel();
135    javax.swing.JLabel statusText2 = new javax.swing.JLabel();
136    javax.swing.JLabel compareErr = new javax.swing.JLabel();
137
138    DiagnosticFrame curFrame;
139
140    private CMRISystemConnectionMemo _memo = null;
141
142    public DiagnosticFrame(CMRISystemConnectionMemo memo) {
143        super();
144        curFrame = this;
145        _memo=memo;
146    }
147
148    /**
149     * {@inheritDoc}
150     */
151    @Override
152    public void initComponents() {
153
154        initializeNodes();
155        nodeSelBox.setEditable(false);
156        if (numTestNodes > 0) {
157            nodeSelBox.addActionListener(new ActionListener() {
158                @Override
159                public void actionPerformed(ActionEvent event) {
160                    displayNodeInfo((String) nodeSelBox.getSelectedItem());
161                }
162            });
163        }
164
165        // set the frame's initial state
166        setTitle(Bundle.getMessage("DiagnosticTitle") + Bundle.getMessage("WindowConnectionMemo") + _memo.getUserName());  // NOI18N
167        setSize(500, 200);
168        Container contentPane = getContentPane();
169        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
170
171        // Test node information
172        //----------------------
173        JPanel panelNode = new JPanel();
174        panelNode.setLayout(new BoxLayout(panelNode, BoxLayout.Y_AXIS));
175        JPanel panelNode1 = new JPanel();
176        panelNode1.setLayout(new FlowLayout());
177        panelNode1.add(new JLabel(Bundle.getMessage("LabelNodeAddress")));
178        panelNode1.add(nodeSelBox);
179        nodeSelBox.setToolTipText(Bundle.getMessage("SelectNodeAddressTip"));
180        panelNode1.add(nodeText1);
181        nodeText1.setText("Node Type/Card Size");
182        panelNode.add(panelNode1);
183
184        JPanel panelNode2 = new JPanel();
185        panelNode2.setLayout(new FlowLayout());
186        nodeText2.setText("Ins and Outs");
187        panelNode2.add(nodeText2);
188        panelNode.add(panelNode2);
189
190        Border panelNodeBorder = BorderFactory.createEtchedBorder();
191        Border panelNodeTitled = BorderFactory.createTitledBorder(panelNodeBorder, Bundle.getMessage("TestNodeTitle"));
192        panelNode.setBorder(panelNodeTitled);
193        contentPane.add(panelNode);
194
195        // Set up the test suite buttons
196        //------------------------------
197        JPanel panelTest = new JPanel();
198        panelTest.setLayout(new BoxLayout(panelTest, BoxLayout.Y_AXIS));
199        JPanel panelTest1 = new JPanel();
200        panelTest1.setLayout(new FlowLayout(FlowLayout.LEADING));
201        testSelectBox.addItem(Bundle.getMessage("ButtonTestOutput"));
202        testSelectBox.addItem(Bundle.getMessage("ButtonTestLoopback"));
203        testSelectBox.addItem(Bundle.getMessage("ButtonTestSendCommands"));
204        panelTest1.add(testSelectBox);
205        testSelectBox.setToolTipText(Bundle.getMessage("TestTypeToolLabel"));
206
207        // --------------------------
208        // Set up Halt Polling button
209        // --------------------------
210        haltPollButton.setVisible(true);
211        haltPollButton.setToolTipText(Bundle.getMessage("HaltPollButtonTip"));
212        haltPollButton.addActionListener(new java.awt.event.ActionListener() {
213            @Override
214            public void actionPerformed(java.awt.event.ActionEvent e) {
215                haltpollButtonActionPerformed();
216            }
217        });
218        panelTest1.add(haltPollButton);
219        SerialTrafficController stc = _memo.getTrafficController();
220        if (stc.getPollNetwork()) {
221            haltPollButton.setText(Bundle.getMessage("HaltPollButtonText"));
222        } else {
223            haltPollButton.setText(Bundle.getMessage("ResumePollButtonText"));
224        }
225
226        panelTest.add(panelTest1);
227
228        JPanel panel11 = new JPanel();
229        panel11.setLayout(new FlowLayout(FlowLayout.LEFT));
230        testReqEquip.setText(Bundle.getMessage("NeededEquipmentTitle"));
231        panel11.add(testReqEquip);
232        panel11.add(testEquip);
233        testEquip.setToolTipText(Bundle.getMessage("NeededTestEquipmentTip"));
234        panelTest.add(panel11);
235
236        Border panel1Border = BorderFactory.createEtchedBorder();
237        Border panel1Titled = BorderFactory.createTitledBorder(panel1Border, Bundle.getMessage("TestTypeTitle"));
238        panelTest.setBorder(panel1Titled);
239        contentPane.add(panelTest);
240
241        // Set up the test setup panel
242        // There are multiple panes depending upon which test type is selected
243        //--------------------------------------------------------------------
244        JPanel panel2 = new JPanel();
245        panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
246
247        // Panel for the Output test suite
248        JPanel panel21 = new JPanel();
249        panel21.setLayout(new FlowLayout());
250        panel21.add(new JLabel("  " + Bundle.getMessage("OutCardLabel")));
251        panel21.add(outCardField);
252        outCardField.setToolTipText(Bundle.getMessage("OutCardToolTip"));
253        outCardField.setText("0");
254        panel21.add(invertOutButton);
255        invertOutButton.setToolTipText(Bundle.getMessage("InvertToolTip"));
256        panel21.add(new JLabel("   " + Bundle.getMessage("ObservationDelayLabel")));
257        panel21.add(obsDelayField);
258        obsDelayField.setToolTipText(Bundle.getMessage("ObservationDelayToolTip"));
259        obsDelayField.setText(Integer.toString(obsDelay));
260
261        // Panel for the Loopback test
262        JPanel panel22 = new JPanel();
263        panel22.setLayout(new FlowLayout());
264        panel22.add(new JLabel(Bundle.getMessage("InCardToolLabel")));
265        panel22.add(inCardField);
266        panel22.add(invertWrapButton);
267        invertWrapButton.setToolTipText(Bundle.getMessage("InvertToolTip"));
268        inCardField.setToolTipText(Bundle.getMessage("InCardToolTip"));
269        inCardField.setText("2");
270        panel22.add(new JLabel("   " + Bundle.getMessage("FilteringDelayLabel")));
271        panel22.add(filterDelayField);
272        filterDelayField.setToolTipText(Bundle.getMessage("FilteringDelayToolTip"));
273        filterDelayField.setText("0");
274
275        // Panel for the Node command packets
276        JPanel panel23 = new JPanel();
277        panel23.setLayout(new FlowLayout(FlowLayout.LEFT));
278        panel23.add(initButton);
279        initButton.addActionListener(new java.awt.event.ActionListener() {
280            @Override
281            public void actionPerformed(java.awt.event.ActionEvent e) {
282                sendInitalizePacket();
283            }
284        });
285        pollButton.addActionListener(new java.awt.event.ActionListener() {
286            @Override
287            public void actionPerformed(java.awt.event.ActionEvent e) {
288                pollButtonActionPerformed(e);
289            }
290        });
291
292        JPanel panel23a = new JPanel();
293        panel23a.setLayout(new FlowLayout(FlowLayout.LEFT));
294        panel23a.add(pollButton);
295        panel23a.add(nodeReplyLabel);
296        panel23a.add(nodeReplyText);
297
298        JPanel panel24 = new JPanel();
299        panel24.setLayout(new FlowLayout(FlowLayout.LEFT));
300        panel24.add(writeButton);
301        writeButton.addActionListener(new java.awt.event.ActionListener() {
302            @Override
303            public void actionPerformed(java.awt.event.ActionEvent e) {
304                sendButtonActionPerformed(e);
305            }
306        });
307        panel24.add(writeCardLabel);
308        panel24.add(writeCardField);
309        writeCardField.setText("0");
310        panel24.add(invertWriteButton);
311        panel24.add(writeBytesLabel);
312        panel24.add(writeBytesField);
313        writeBytesField.setText("0");
314
315        // Panel for the Poll node with inputs display
316        JPanel panel25 = new JPanel();
317        panel25.setLayout(new FlowLayout());
318
319        panel2.add(panel21);
320
321        panel2.add(panel22);
322        panel22.setVisible(false);
323
324        panel2.add(panel23);
325        panel23.setVisible(false);
326        panel2.add(panel23a);
327        panel23a.setVisible(false);
328        panel2.add(panel24);
329        panel24.setVisible(false);
330
331        panel2.add(panel25);
332        panel25.setVisible(false);
333
334        Border panel2Border = BorderFactory.createEtchedBorder();
335        Border panel2Titled = BorderFactory.createTitledBorder(panel2Border, Bundle.getMessage("TestSetUpTitle"));
336        panel2.setBorder(panel2Titled);
337        contentPane.add(panel2);
338
339        // Add the button listeners to display the appropriate test options
340        //-----------------------------------------------------------------
341        testSelectBox.addActionListener(new ActionListener() {
342            @Override
343            public void actionPerformed(ActionEvent event) {
344                selTestType = testSelectBox.getSelectedIndex();
345                switch (selTestType) {
346                    case testType_Outputs:
347                        testEquip.setText(Bundle.getMessage("OutputTestEquipment"));
348                        panel21.setVisible(true);
349                        panel22.setVisible(false);
350                        panel23.setVisible(false);
351                        panel23a.setVisible(false);
352                        panel24.setVisible(false);
353                        panel25.setVisible(false);
354                        runButton.setEnabled(true);
355                        stopButton.setEnabled(true);
356                        continueButton.setVisible(false);
357                        displayNodeInfo(testNodeID);
358                        break;
359                    case testType_Wraparound:
360                        testEquip.setText(Bundle.getMessage("WrapTestEquipment"));
361                        panel21.setVisible(true);
362                        panel22.setVisible(true);
363                        panel23.setVisible(false);
364                        panel23a.setVisible(false);
365                        panel24.setVisible(false);
366                        panel25.setVisible(false);
367                        invertOutButton.setVisible(false);
368                        runButton.setEnabled(true);
369                        stopButton.setEnabled(true);
370                        continueButton.setVisible(true);
371                        invertWrapButton.setSelected(testNodeType == SerialNode.CPNODE);
372                        displayNodeInfo(testNodeID);
373                        break;
374                    case testType_SendCommand:
375                        testEquip.setText(Bundle.getMessage("SendCommandEquipment"));
376                        panel21.setVisible(false);
377                        panel22.setVisible(false);
378                        panel23.setVisible(true);
379                        panel23a.setVisible(true);
380                        panel24.setVisible(true);
381                        panel25.setVisible(false);
382                        runButton.setEnabled(false);
383                        stopButton.setEnabled(false);
384                        continueButton.setVisible(false);
385                        displayNodeInfo(testNodeID);
386                        break;
387                    case testType_WriteBytes:
388                        testEquip.setText(Bundle.getMessage("WriteBytesEquipment"));
389                        panel21.setVisible(false);
390                        panel22.setVisible(false);
391                        panel23.setVisible(false);
392                        panel23a.setVisible(false);
393                        panel24.setVisible(false);
394                        panel25.setVisible(true);
395                        displayNodeInfo(testNodeID);
396                        break;
397                    default:
398                        log.debug("default case in testSelectBox switch");
399                }
400            }
401        });
402
403        // Set up the status panel
404        //------------------------
405        JPanel panel3 = new JPanel();
406        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
407
408        JPanel panel31 = new JPanel();
409        panel31.setLayout(new FlowLayout());
410        statusText1.setText(Bundle.getMessage("StatusLine1"));
411        statusText1.setVisible(true);
412        statusText1.setMaximumSize(new Dimension(statusText1.getMaximumSize().width,
413                statusText1.getPreferredSize().height));
414        panel31.add(statusText1);
415
416        JPanel panel32 = new JPanel();
417        panel32.setLayout(new FlowLayout());
418        statusText2.setText(Bundle.getMessage("StatusLine2", Bundle.getMessage("ButtonRun")));
419        statusText2.setVisible(true);
420        statusText2.setMaximumSize(new Dimension(statusText2.getMaximumSize().width,
421                statusText2.getPreferredSize().height));
422        panel32.add(statusText2);
423
424        JPanel panel33 = new JPanel();
425        panel33.setLayout(new FlowLayout());
426        compareErr.setText("   "); //Bundle.getMessage("StatusLine1"));
427        compareErr.setVisible(true);
428        compareErr.setMaximumSize(new Dimension(compareErr.getMaximumSize().width,
429                compareErr.getPreferredSize().height));
430        panel33.add(compareErr);
431
432        panel3.add(panel31);
433        panel3.add(panel32);
434        panel3.add(panel33);
435
436        Border panel3Border = BorderFactory.createEtchedBorder();
437        Border panel3Titled = BorderFactory.createTitledBorder(panel3Border, Bundle.getMessage("StatusTitle"));
438        panel3.setBorder(panel3Titled);
439        contentPane.add(panel3);
440
441        // Set up Continue, Stop, Run buttons
442        //-----------------------------------
443        JPanel panel4 = new JPanel();
444        panel4.setLayout(new FlowLayout());
445        continueButton.setText(Bundle.getMessage("ButtonContinue"));
446        continueButton.setVisible(false);
447        continueButton.setToolTipText(Bundle.getMessage("ContinueTestToolTip"));
448        continueButton.addActionListener(new java.awt.event.ActionListener() {
449            @Override
450            public void actionPerformed(java.awt.event.ActionEvent e) {
451                continueButtonActionPerformed(e);
452            }
453        });
454        panel4.add(continueButton);
455        stopButton.setText(Bundle.getMessage("ButtonStop"));
456        stopButton.setVisible(true);
457        stopButton.setToolTipText(Bundle.getMessage("StopToolTip"));
458        panel4.add(stopButton);
459        stopButton.addActionListener(new java.awt.event.ActionListener() {
460            @Override
461            public void actionPerformed(java.awt.event.ActionEvent e) {
462                stopButtonActionPerformed(e);
463            }
464        });
465        runButton.setText(Bundle.getMessage("ButtonRun"));
466        runButton.setVisible(true);
467        runButton.setToolTipText(Bundle.getMessage("RunTestToolTip"));
468        panel4.add(runButton);
469        runButton.addActionListener(new java.awt.event.ActionListener() {
470            @Override
471            public void actionPerformed(java.awt.event.ActionEvent e) {
472                runButtonActionPerformed(e);
473            }
474        });
475        contentPane.add(panel4);
476
477        if (numTestNodes > 0) {
478            // initialize for the first time
479            displayNodeInfo((String) nodeSelBox.getSelectedItem());
480        }
481        testSelectBox.setSelectedIndex(selTestType);
482        addHelpMenu("package.jmri.jmrix.cmri.serial.diagnostic.DiagnosticFrame", true);
483
484        // pack for display
485        pack();
486    }
487
488    /**
489     * Initialize configured nodes and set up the node select combo box.
490     */
491    public void initializeNodes() {
492        String str = "";
493        // clear the arrays
494        for (int i = 0; i < 128; i++) {
495            testNodeAddresses[i] = -1;
496            testNodes[i] = null;
497        }
498        // get all configured nodes
499        SerialNode node = (SerialNode) _memo.getTrafficController().getNode(0);
500        int index = 1;
501        while (node != null)
502        {
503            testNodes[numTestNodes] = node;
504            testNodeAddresses[numTestNodes] = node.getNodeAddress();
505            str = Integer.toString(testNodeAddresses[numTestNodes]);
506            nodeSelBox.addItem(str);
507            if (index == 1) {
508                testNode = node;
509                testNodeAddr = testNodeAddresses[numTestNodes];
510                testNodeID = "y";  // to force first time initialization
511            }
512            numTestNodes++;
513            // go to next node
514            node = (SerialNode) _memo.getTrafficController().getNode(index);
515            index++;
516        }
517    }
518    
519    /**
520     * Method to handle selection of a Node for info display.
521     * @param nodeID Node ID.
522     */
523    public void displayNodeInfo(String nodeID) {
524        if (!nodeID.equals(testNodeID)) {
525            // The selected node is changing - initialize it
526            int aTestNum = Integer.parseInt(nodeID);
527            SerialNode s = null;
528            for (int k = 0; k < numTestNodes; k++) {
529                if (aTestNum == testNodeAddresses[k]) {
530                    s = testNodes[k];
531                }
532            }
533            if (s == null) {
534                // serious trouble, log error and ignore
535                log.error("Cannot find Node {} in list of configured Nodes.", nodeID);
536                return;
537            }
538            // have node, initialize for new node
539            testNodeID = nodeID;
540            testNode = s;
541            testNodeAddr = aTestNum;
542            // prepare the information line
543            int bitsPerCard = testNode.getNumBitsPerCard();
544//            int numInputCards = testNode.numInputCards();
545//            int numOutputCards = testNode.numOutputCards();
546//            int numIOXInputCards = 0;
547//            int numIOXOutputCards= 0;
548           
549            testNodeType = testNode.getNodeType();
550            String s1 = "",
551                   s2 = "";
552            switch (testNodeType)
553             {        
554                case SerialNode.SMINI:
555                  bitsPerCard = testNode.getNumBitsPerCard();
556                  numInputCards = testNode.numInputCards();
557                  numOutputCards = testNode.numOutputCards();
558                  numIOXInputCards = 0;
559                  numIOXOutputCards= 0;
560
561                  nodeText1.setText("  SMINI - " + bitsPerCard + " " + Bundle.getMessage("BitsPerCard"));
562                  nodeText2.setText(numInputCards + " " + Bundle.getMessage("InputCard") +
563                                    ", " + numOutputCards + " " + Bundle.getMessage("OutputCard") + "s");
564                break;
565                case SerialNode.USIC_SUSIC:
566                  bitsPerCard = testNode.getNumBitsPerCard();
567                  numInputCards = testNode.numInputCards();
568                  numOutputCards = testNode.numOutputCards();
569                  numIOXInputCards = 0;
570                  numIOXOutputCards= 0;
571                  if(numInputCards > 1) s1 = "s";
572                  if(numOutputCards > 1) s2 = "s";
573                  nodeText1.setText("  USIC_SUSIC - " + bitsPerCard + " " + Bundle.getMessage("BitsPerCard"));
574                  nodeText2.setText(numInputCards + " " + Bundle.getMessage("InputCard") + s1 +
575                                    ", " + numOutputCards + " " + Bundle.getMessage("OutputCard") + s2);
576                break;
577                case SerialNode.CPNODE:
578                  bitsPerCard = testNode.getNumBitsPerCard();
579                  numInputCards = testNode.numInputCards(); //2;
580                  numOutputCards = testNode.numOutputCards(); //2;
581                  numIOXInputCards = testNode.numInputCards() - 2;
582                  numIOXOutputCards= testNode.numOutputCards()- 2;
583                  if(numInputCards > 1) s1 = "s";
584                  if(numOutputCards > 1) s2 = "s";
585                  nodeText1.setText("  CPNODE - " + bitsPerCard + " " +Bundle.getMessage("BitsPerCard"));
586                  nodeText2.setText(numInputCards + " " + Bundle.getMessage("InputCard") + s1 +
587                                    ", " + numOutputCards + " " + Bundle.getMessage("OutputCard") + s2 +
588                                    "  IOX: " + numIOXInputCards + " " + Bundle.getMessage("InputsTitle") + 
589                                    ", " + numIOXOutputCards + " " + Bundle.getMessage("OutputsTitle"));
590                  invertWrapButton.setSelected(testNodeType == SerialNode.CPNODE);
591                break;
592                case SerialNode.CPMEGA:
593                  numIOXInputCards = 0;
594                  numIOXOutputCards= 0;
595                  nodeText1.setText("CPMEGA - " + bitsPerCard + " " + Bundle.getMessage("BitsPerCard"));
596                break;
597                case SerialNode.ESP32NODE:
598                  // No onboard cards at all (unlike CPNODE's 2+2), so unlike CPNODE's
599                  // "numOutputCards - 2" here, IOX counts equal the raw counts directly.
600                  bitsPerCard = testNode.getNumBitsPerCard();
601                  numInputCards = testNode.numInputCards();
602                  numOutputCards = testNode.numOutputCards();
603                  numIOXInputCards = numInputCards;
604                  numIOXOutputCards= numOutputCards;
605                  if(numInputCards > 1) s1 = "s";
606                  if(numOutputCards > 1) s2 = "s";
607                  nodeText1.setText("  ESP32Node - " + bitsPerCard + " " +Bundle.getMessage("BitsPerCard"));
608                  nodeText2.setText(numInputCards + " " + Bundle.getMessage("InputCard") + s1 +
609                                    ", " + numOutputCards + " " + Bundle.getMessage("OutputCard") + s2 +
610                                    "  IOX: " + numIOXInputCards + " " + Bundle.getMessage("InputsTitle") +
611                                    ", " + numIOXOutputCards + " " + Bundle.getMessage("OutputsTitle"));
612                break;
613                default:
614                  nodeText1.setText("Unknown Node Type "+testNodeType);
615                break;            
616            }
617// here insert code for new types of C/MRI nodes
618        }
619        statusText1.setVisible(true);
620        statusText2.setVisible(true);
621
622    }
623    
624    /**
625     * Handle run button in Diagnostic Frame.
626     * @param e unused.
627     */
628    public void runButtonActionPerformed(java.awt.event.ActionEvent e) {
629        // Ignore button if test is already running
630        if (!testRunning) {
631            // Read the user entered data, and report any errors
632            if (readSetupData()) {
633                if (outTest) {
634                    // Initialize output test
635                    if (initializeOutputTest()) {
636                        // Run output test
637                        runOutputTest();
638                    }
639                } else if (wrapTest) {
640                    // Initialize wraparound test
641                    if (initializeWraparoundTest()) {
642                        // Run wraparound test
643                        runWraparoundTest();
644                    }
645                }
646            }
647        }
648    }
649
650    /**
651     * Read data in Diagnostic Frame, get node data, and test
652     * for consistency.
653     * If errors are found, the errors are noted in the status panel
654     * of the Diagnostic Frame.
655     *
656     * @return 'true' if no errors are found, 'false' if errors are found
657     */
658    protected boolean readSetupData() {
659        // determine test type
660//        outTest = outputButton.isSelected();
661//        wrapTest = wrapButton.isSelected();
662        switch(selTestType)
663        {
664            case testType_Outputs:
665                outTest = true;
666                wrapTest= false;
667                break;
668            case testType_Wraparound:
669                outTest = false;
670                wrapTest= true;
671                break;
672            case testType_SendCommand:
673            case testType_WriteBytes:
674                outTest = false;
675                wrapTest= false;
676                break;
677            default:
678                log.debug("default case in testSelectBox switch");
679        }
680        
681        // get the SerialNode corresponding to this node address
682        testNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(testNodeAddr);
683        if (testNode == null) {
684            statusText1.setText(Bundle.getMessage("DiagnosticError3"));
685            statusText1.setVisible(true);
686            return (false);
687        }
688        // determine if node is SMINI, USIC_SUSIC, or
689        int type = testNode.getNodeType();
690        isSMINI = (type == SerialNode.SMINI);
691        isUSIC_SUSIC = (type == SerialNode.USIC_SUSIC);
692        isCPNODE = (type == SerialNode.CPNODE);
693        isESP32NODE = (type == SerialNode.ESP32NODE);
694        // Here insert code for other type nodes
695        // initialize numInputCards, numOutputCards, and numCards
696        numOutputCards = testNode.numOutputCards();
697        numInputCards = testNode.numInputCards();
698        numCards = numOutputCards + numInputCards;
699
700        // read setup data - Out Card field
701        try {
702            outCardNum = Integer.parseInt(outCardField.getText());
703        } catch (Exception e) {
704            statusText1.setText(Bundle.getMessage("DiagnosticError4"));
705            statusText1.setVisible(true);
706            return (false);
707        }
708        // Check for consistency with Node definition
709        if (isUSIC_SUSIC) {
710            if ((outCardNum < 0) || (outCardNum >= numCards)) {
711                statusText1.setText(Bundle.getMessage("DiagnosticError5", Integer.toString(numCards - 1)));
712                statusText1.setVisible(true);
713                return (false);
714            }
715            if (!testNode.isOutputCard(outCardNum)) {
716                statusText1.setText(Bundle.getMessage("DiagnosticError6"));
717                statusText1.setVisible(true);
718                return (false);
719            }
720        }
721        if (isSMINI && ((outCardNum < 0) || (outCardNum > 1))) {
722            statusText1.setText(Bundle.getMessage("DiagnosticError7"));
723            statusText1.setVisible(true);
724            return (false);
725        }
726        if (isCPNODE && (!testNode.isOutputCard(outCardNum+2))) {
727            statusText1.setText(Bundle.getMessage("DiagnosticError6"));
728            statusText1.setVisible(true);
729            return (false);
730        }
731        // ESP32Node's typed Out Card field is 1-based (Card 1 = the first real
732        // card, 0x20/A) to match its on-screen "Card N" label -- unlike
733        // CPNODE's field above, which is 0-based internally even though its
734        // label shows +2. So the typed number converts to a raw
735        // cardTypeLocation index via -1, not +2 or +1. Card 1 typed -> raw
736        // index 0. Guard against 0 or negative input first (raw index -1
737        // would be an ArrayIndexOutOfBoundsException, not just "not a card").
738        if (isESP32NODE && (outCardNum < 1)) {
739            statusText1.setText(Bundle.getMessage("DiagnosticError6"));
740            statusText1.setVisible(true);
741            return (false);
742        }
743        if (isESP32NODE && (!testNode.isOutputCard(outCardNum-1))) {
744            statusText1.setText(Bundle.getMessage("DiagnosticError6"));
745            statusText1.setVisible(true);
746            return (false);
747        }
748        
749        if (outTest) {
750            // read setup data - Observation Delay field
751            try {
752                obsDelay = Integer.parseInt(obsDelayField.getText());
753            } catch (Exception e) {
754                statusText1.setText(Bundle.getMessage("DiagnosticError8"));
755                statusText1.setVisible(true);
756                return (false);
757            }
758        }
759
760        if (wrapTest) {
761            // read setup data - In Card field
762            try {
763                inCardNum = Integer.parseInt(inCardField.getText());
764            } catch (Exception e) {
765                statusText1.setText(Bundle.getMessage("DiagnosticError9"));
766                statusText1.setVisible(true);
767                return (false);
768            }
769            // Check for consistency with Node definition
770            if (isUSIC_SUSIC) {
771                if ((inCardNum < 0) || (inCardNum >= numCards)) {
772                    statusText1.setText(Bundle.getMessage("DiagnosticError10", Integer.toString(numCards - 1)));
773                    statusText1.setVisible(true);
774                    return (false);
775                }
776                if (!testNode.isInputCard(inCardNum)) {
777                    statusText1.setText(Bundle.getMessage("DiagnosticError11"));
778                    statusText1.setVisible(true);
779                    return (false);
780                }
781            }
782            if (isSMINI && (inCardNum != 2)) {
783                statusText1.setText(Bundle.getMessage("DiagnosticError12"));
784                statusText1.setVisible(true);
785                return (false);
786            }
787
788            // read setup data - Filtering Delay field
789            try {
790                filterDelay = Integer.parseInt(filterDelayField.getText());
791            } catch (Exception e) {
792                statusText1.setText(Bundle.getMessage("DiagnosticError13"));
793                statusText1.setVisible(true);
794                return (false);
795            }
796        }
797
798        // complete initialization of output card
799        portsPerCard = (testNode.getNumBitsPerCard()) / 8;
800
801        if (testNodeType == SerialNode.CPNODE)
802         begOutByte = (testNode.getOutputCardIndex(outCardNum+2)) * portsPerCard;
803        else if (testNodeType == SerialNode.ESP32NODE)
804         begOutByte = (testNode.getOutputCardIndex(outCardNum-1)) * portsPerCard;
805        else
806         begOutByte = (testNode.getOutputCardIndex(outCardNum)) * portsPerCard;
807
808        endOutByte = begOutByte + portsPerCard - 1;
809        nOutBytes = numOutputCards * portsPerCard;
810
811        // if wraparound test, complete initialization of the input card
812        if (wrapTest) {
813            begInByte = (testNode.getInputCardIndex(inCardNum)) * portsPerCard;
814            endInByte = begInByte + portsPerCard - 1;
815            nInBytes = numInputCards * portsPerCard;
816        }
817        return (true);
818    }
819
820    /**
821     * Handle continue button in Diagnostic Frame.
822     * @param e unused.
823     */
824    public void continueButtonActionPerformed(java.awt.event.ActionEvent e) {
825        if (testRunning && testSuspended) {
826            testSuspended = false;
827            if (wrapTest) {
828                statusText1.setText(Bundle.getMessage("StatusRunningWraparoundTest"));
829                statusText1.setVisible(true);
830            }
831        }
832    }
833
834    /**
835     * Handle Stop button in Diagnostic Frame.
836     * @param e unused.
837     */
838    public void stopButtonActionPerformed(java.awt.event.ActionEvent e) {
839        // Ignore button push if test is not running, else change flag
840        if (testRunning) {
841            if (outTest) {
842                stopOutputTest();
843            } else if (wrapTest) {
844                stopWraparoundTest();
845            }
846            testRunning = false;
847        }
848    }
849
850    /**
851     * Halt Poll button handler
852     * Polling should be halted when executing diagnostics so as not to
853     * interfere with the test sequences.  
854     */
855    public void haltpollButtonActionPerformed() {
856         SerialTrafficController stc = _memo.getTrafficController();
857         stc.setPollNetwork(!stc.getPollNetwork());
858         if (stc.getPollNetwork())
859            haltPollButton.setText(Bundle.getMessage("HaltPollButtonText"));
860         else
861            haltPollButton.setText(Bundle.getMessage("ResumePollButtonText"));
862    }
863/**
864     * Initialize an Output Test.
865     * If errors are found, the errors are noted in the status panel of the Diagnostic Frame.
866     *
867     * @return 'true' if successfully initialized, 'false' if errors are found
868     * Added synchronized
869     */
870    synchronized protected boolean initializeOutputTest() {
871        // clear all output bytes for this node
872        for (int i = 0; i < nOutBytes; i++) {
873            outBytes[i] = 0;
874        }
875        // check the entered delay--if too short an overrun could occur
876        // where the computer program is ahead of buffered serial output
877        if (obsDelay < 250) {
878            obsDelay = 250;
879        }
880        // Set up beginning LED on position
881        curOutByte = begOutByte;
882        curOutBit = 0;
883        // Send initialization message
884        _memo.getTrafficController().sendSerialMessage((SerialMessage) testNode.createInitPacket(), curFrame);
885        try {
886            // Wait for initialization to complete
887            wait(1000);
888        } catch (InterruptedException e) {
889            // means done
890            log.debug("interrupted");
891            return false;
892        }
893        // Initialization was successful
894        numIterations = 0;
895        testRunning = true;
896        return true;
897    }
898
899    /**
900     * Run an Output Test.
901     */
902    protected void runOutputTest() {
903        // Set up timer to update output pattern periodically
904        outTimer = new Timer(obsDelay, new ActionListener() {
905            @Override
906            public void actionPerformed(ActionEvent evnt) {
907                if (testRunning && outTest) {
908                    int[] outBitPattern = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
909                    String[] portID = {"A", "B", "C", "D"};
910                    
911                    // set new pattern
912                    // Invert bit polarity if selected (usefull for Common Anode LEDs)
913                    if (invertOutButton.isSelected())
914                     for (int i=0; i<8; i++) { outBitPattern[i] = (~outBitPattern[i]); }
915                    
916                    outBytes[curOutByte] = (byte) outBitPattern[curOutBit];
917                    // send new pattern
918                    SerialMessage m = createOutPacket();
919                    m.setTimeout(50);
920                    _memo.getTrafficController().sendSerialMessage(m, curFrame);
921                    // update status panel to show bit that is on
922                    statusText1.setText(Bundle.getMessage("StatusLine3", portID[curOutByte - begOutByte], Integer.toString(curOutBit)));
923                    statusText1.setVisible(true);
924                    StringBuilder st = new StringBuilder();
925                    for (int i = begOutByte; i <= endOutByte; i++) {
926                        st.append("  ");
927                      for (int j = 0; j < 8; j++) {
928                            if ((i == curOutByte) && (j == curOutBit)) {
929                                st.append("1 ");
930                            } else {
931                                st.append("0 ");
932                            }
933                        }
934                    }
935                    // Was st.reverse().toString() -- that reversed the WHOLE
936                    // string char-by-char, which flipped bit order within
937                    // each byte to MSB-left/LSB-right AND (for multi-byte
938                    // cards) reversed the order the byte-groups themselves
939                    // appeared in. As curOutBit climbed 0->7, the "1" in the
940                    // reversed string moved right-to-left, not left-to-right.
941                    // Dropping the reversal displays the string exactly as
942                    // built above: bit0 leftmost within each byte (ascending,
943                    // matching the convention used elsewhere in this project),
944                    // byte begOutByte's group leftmost for multi-byte cards --
945                    // so the walking-bit test now visibly sweeps left to right.
946                    statusText2.setText(st.toString()); //statusText2
947                    statusText2.setVisible(true);
948                    // update bit pattern for next entry
949                    curOutBit++;
950                    if (curOutBit > 7) {
951                        // Move to the next byte
952                        curOutBit = 0;
953                        outBytes[curOutByte] = 0;
954                        curOutByte++;
955                        if (curOutByte > endOutByte) {
956                            // Pattern complete, recycle to first byte
957                            curOutByte = begOutByte;
958                            numIterations++;
959                        }
960                    }
961                }
962            }
963        });
964
965        // start timer
966        outTimer.start();
967    }
968
969    /**
970     * Stop an Output Test.
971     */
972    protected void stopOutputTest() {
973        if (testRunning && outTest) {
974            // Stop the timer
975            outTimer.stop();
976            // Update the status
977            statusText1.setText(Bundle.getMessage("StatusLine4", Integer.toString(numIterations)));
978            statusText1.setVisible(true);
979            statusText2.setText("  ");
980            statusText2.setVisible(true);
981        }
982    }
983    
984    /**
985     * Transmit an Initialize message to the test node.
986     * 
987     * @return 'true' if message sent successfully
988     */
989    synchronized protected boolean sendInitalizePacket() {
990         // Send initialization message
991        _memo.getTrafficController().sendSerialMessage((SerialMessage) testNode.createInitPacket(), curFrame);
992        try {
993            // Wait for initialization to complete
994            wait(1000);
995        } catch (InterruptedException e) {
996            log.debug("interrupted");
997            return false;
998        }
999
1000        return true;
1001    }
1002
1003    /**
1004     * Initialize a Wraparound Test.
1005     * If errors are found, the errors are noted in the status panel of the Diagnostic
1006     * Frame.
1007     *
1008     * @return 'true' if successfully initialized, 'false' if errors are found
1009     */
1010    synchronized protected boolean initializeWraparoundTest() {
1011        // clear all output bytes for this node
1012        for (int i = 0; i < nOutBytes; i++) {
1013            outBytes[i] = 0;
1014        }
1015        // Set up beginning output values
1016        curOutByte = begOutByte;
1017        curOutValue = 0;
1018        
1019        if (!sendInitalizePacket())
1020         return false; 
1021        
1022        // Clear error count
1023        numErrors = 0;
1024        numIterations = 0;
1025        // Initialize running flags
1026        testRunning = true;
1027        testSuspended = false;
1028        waitingOnInput = false;
1029        needInputTest = false;
1030        count = 50;
1031        compareErr.setText("  ");
1032
1033        return true;
1034    }
1035
1036    /**
1037     * Run a Wraparound Test.
1038     */
1039    protected void runWraparoundTest() {
1040        // Display Status Message
1041        statusText1.setText(Bundle.getMessage("StatusRunningWraparoundTest"));
1042        statusText1.setVisible(true);
1043
1044        // Set up timer to update output pattern periodically
1045        wrapTimer = new Timer(100, new ActionListener() {
1046            @Override
1047            public void actionPerformed(ActionEvent evnt) {
1048                if (testRunning && !testSuspended) {
1049                    if (waitingOnInput) {
1050                        count--;
1051                        if (count == 0) {
1052                            statusText2.setText(Bundle.getMessage("StatusLine5"));
1053                            statusText2.setVisible(true);
1054                        }
1055                    } else {
1056                        // compare input with previous output if needed
1057                        if (needInputTest) {
1058                            needInputTest = false;
1059                            boolean comparisonError = false;
1060                            // compare input and output bytes
1061                            int j = 0;
1062                            for (int i = begInByte; i <= endInByte; i++, j++) 
1063                            {
1064                                if (invertWrapButton.isSelected()) { inBytes[i] = (byte) ~inBytes[j];                               
1065                                 }
1066                                
1067                                if (inBytes[i] != wrapBytes[j]) {
1068                                    comparisonError = true;                                
1069                                }
1070                            }
1071                            if (comparisonError) {
1072                                // report error and suspend test
1073                                statusText1.setText(Bundle.getMessage("StatusLine6",
1074                                Bundle.getMessage("ButtonStop"), Bundle.getMessage("ButtonContinue")));
1075                                statusText1.setVisible(true);
1076                                StringBuilder st = new StringBuilder(Bundle.getMessage("StatusLine7pt1"));
1077                                for (int i = begOutByte; i <= endOutByte; i++) {
1078                                    st.append(" ");
1079                                    st.append(Integer.toHexString((outBytes[i]) & 0x000000ff).toUpperCase());
1080                                }
1081                                st.append("    "); // spacer
1082                                st.append(Bundle.getMessage("StatusLine7pt2"));
1083                                for (int i = begInByte; i <= endInByte; i++) {
1084                                    st.append(" ");
1085                                    st.append(Integer.toHexString((inBytes[i]) & 0x000000ff).toUpperCase());
1086                                }
1087                                compareErr.setText(st.toString()); //statusText2
1088                                compareErr.setVisible(true);
1089                                numErrors++;
1090                                testSuspended = true;
1091                                return;
1092                            }
1093                        }                         
1094
1095                        // send next output pattern
1096                        outBytes[curOutByte] = (byte) curOutValue;
1097                        if (isSMINI) { 
1098                            // If SMINI, send same pattern to both output cards
1099                            if (curOutByte > 2) {
1100                                outBytes[curOutByte - 3] = (byte) curOutValue;
1101                            } else {
1102                                outBytes[curOutByte + 3] = (byte) curOutValue;
1103                            }
1104                        }
1105                        SerialMessage m = createOutPacket();
1106                        // wait for signal to settle down if filter delay
1107                        m.setTimeout(50 + filterDelay);
1108                        _memo.getTrafficController().sendSerialMessage(m, curFrame);
1109
1110                        // update Status area
1111                        short[] outBitPattern = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
1112                        String[] portID = {"A", "B", "C", "D"};
1113                        StringBuilder st = new StringBuilder(Bundle.getMessage("PortLabel"));
1114                        StringBuilder bp = new StringBuilder("  ");
1115                        st.append(portID[curOutByte - begOutByte]);
1116                        st.append(",  ");
1117                        st.append(Bundle.getMessage("PatternLabel"));
1118                        for (int j = 0; j < 8; j++) {
1119                            if ((curOutValue & outBitPattern[j]) != 0) {
1120                                bp.append("1 ");
1121                            } else {
1122                                bp.append("0 ");
1123                            }
1124                        }
1125                        // Reverse the displayed output string to put bit zero on the right
1126                        //-----------------------------------------------------------------
1127                        statusText2.setText(st.toString()+bp.reverse().toString()); //statusText2
1128                        statusText2.setVisible(true);
1129
1130                        // set up for testing input returned
1131                        int k = 0;
1132                        for (int i = begOutByte; i <= endOutByte; i++, k++) {
1133                            wrapBytes[k] = outBytes[i];
1134                        }
1135                        waitingOnInput = true;
1136                        needInputTest = true;
1137                        count = 50;
1138                        // send poll
1139                        _memo.getTrafficController().sendSerialMessage(
1140                                SerialMessage.getPoll(testNodeAddr), curFrame);
1141
1142                        // update output pattern for next entry
1143                        curOutValue++;
1144                        if (curOutValue > 255) {
1145                            // Move to the next byte
1146                            curOutValue = 0;
1147                            outBytes[curOutByte] = 0;
1148                            if (isSMINI) {
1149                                // If SMINI, clear ports of both output cards
1150                                if (curOutByte > 2) {
1151                                    outBytes[curOutByte - 3] = 0;
1152                                } else {
1153                                    outBytes[curOutByte + 3] = 0;
1154                                }
1155                            }
1156                            curOutByte++;
1157                            if (curOutByte > endOutByte) {
1158                                // Pattern complete, recycle to first port (byte)
1159                                curOutByte = begOutByte;
1160                                numIterations++;
1161                            }
1162                        }
1163                    }
1164                }
1165            }
1166        });
1167
1168        // start timer
1169        wrapTimer.start();
1170    }
1171
1172    /**
1173     * Stop a Wraparound Test.
1174     */
1175    protected void stopWraparoundTest() {
1176        if (testRunning && wrapTest) {
1177            // Stop the timer
1178            wrapTimer.stop();
1179            // Update the status
1180            statusText1.setText(Bundle.getMessage("StatusLine8", Integer.toString(numErrors)));
1181            statusText1.setVisible(true);
1182            statusText2.setText(Bundle.getMessage("StatusLine9", Integer.toString(numIterations)));
1183            statusText2.setVisible(true);
1184        }
1185    }
1186
1187    /**
1188     * Create an Transmit packet (SerialMessage).
1189     * @return loaded packet to transmit
1190     */
1191    SerialMessage createOutPacket() {
1192        // Count the number of DLE's to be inserted
1193        int nDLE = 0;
1194        for (int i = 0; i < nOutBytes; i++) {
1195            if ((outBytes[i] == 2) || (outBytes[i] == 3) || (outBytes[i] == 16)) {
1196                nDLE++;
1197            }
1198        }
1199        // Create a Serial message and add initial bytes
1200        SerialMessage m = new SerialMessage(nOutBytes + nDLE + 2);
1201        m.setElement(0, testNodeAddr + 65);  // node address
1202        m.setElement(1, 84);     // 'T'
1203        // Add output bytes
1204        int k = 2;
1205        for (int i = 0; i < nOutBytes; i++) {
1206            // perform C/MRI required DLE processing
1207            if ((outBytes[i] == 2) || (outBytes[i] == 3) || (outBytes[i] == 16)) {
1208                m.setElement(k, 16);  // DLE
1209                k++;
1210            }
1211            // add output byte
1212            m.setElement(k, outBytes[i]);
1213            k++;
1214        }
1215        return m;
1216    }
1217    
1218    /**
1219     * Handle poll node button in Diagnostic Frame.
1220     * @param e unused.
1221     */
1222    public synchronized void pollButtonActionPerformed(java.awt.event.ActionEvent e) {
1223            portsPerCard = (testNode.getNumBitsPerCard()) / 8;
1224            begInByte = (testNode.getInputCardIndex(inCardNum)) * portsPerCard;
1225            endInByte = begInByte + portsPerCard;
1226            nInBytes = numInputCards * portsPerCard;
1227           
1228            needInputTest = true;
1229            waitingOnInput = true;
1230            waitingResponse = false;
1231            count = 30;
1232                
1233            // send poll
1234            _memo.getTrafficController().sendSerialMessage(SerialMessage.getPoll(testNodeAddr), curFrame);
1235            statusText2.setText(""); 
1236            nodeReplyText.setText(""); 
1237            
1238            // display input data bytes or timeout
1239            pollNodeReadReply();
1240    }
1241
1242    
1243    /**
1244    * Run a Poll/Response Test.
1245    * Returns number of bytes read or a timeout
1246    */
1247    protected synchronized void pollNodeReadReply() {
1248    // Set up timer to poll the node and report data or a timeout
1249        pollTimer = new Timer(100, new ActionListener() {
1250        @Override
1251        public void actionPerformed(ActionEvent evnt) {
1252                if (waitingOnInput) {
1253                    count--;
1254                    if (count == 0) {
1255                        nodeReplyText.setText(Bundle.getMessage("PollTimeOut"));
1256                    waitingOnInput = false;
1257                    pollTimer.stop();
1258                    return;
1259                   }
1260                } 
1261                else 
1262                {
1263                 if (waitingResponse)
1264                    {
1265                     nodeReplyText.setText(Bundle.getMessage("InByteCount",replyCount));
1266                     nodeReplyText.setVisible(true);
1267                     waitingOnInput = false;
1268                     pollTimer.stop();
1269                     return;                        
1270                    }
1271                }
1272            }
1273        });
1274    
1275    // start timer
1276        pollTimer.start();
1277        waitingResponse = true;
1278    }
1279    
1280    /**
1281     * Transmit bytes to selected output card starting with out card number
1282     * for number of bytes entered.
1283     * If inverted checked, data is flipped.
1284     * @param e unused.
1285     */    
1286    public synchronized void sendButtonActionPerformed(java.awt.event.ActionEvent e) {
1287
1288       portsPerCard = (testNode.getNumBitsPerCard()) / 8;
1289       byte b[] = StringUtil.bytesFromHexString(writeBytesField.getText());
1290       totalOutBytes = (numOutputCards*portsPerCard);
1291       statusText1.setText(" ");
1292
1293       // Validate number of bytes entered
1294        if (b.length == 0) {
1295            statusText1.setText(Bundle.getMessage("WriteBytesError1"));
1296            return; 
1297        }
1298        if (b.length > portsPerCard) {
1299            statusText1.setText(Bundle.getMessage("WriteBytesError2",portsPerCard));
1300            return; 
1301        }
1302        outCardNum = Integer.parseInt(writeCardField.getText());        
1303        
1304        if (testNodeType == SerialNode.CPNODE)
1305        {
1306            if (!testNode.isOutputCard(outCardNum+2)) {
1307             statusText1.setText(Bundle.getMessage("DiagnosticError6"));
1308             return;
1309            }
1310            begOutByte = (testNode.getOutputCardIndex(outCardNum+2)) * portsPerCard;
1311        }
1312        else if (testNodeType == SerialNode.ESP32NODE)
1313        {
1314            if ((outCardNum < 1) || (!testNode.isOutputCard(outCardNum-1))) {
1315             statusText1.setText(Bundle.getMessage("DiagnosticError6"));
1316             return;
1317            }
1318            begOutByte = (testNode.getOutputCardIndex(outCardNum-1)) * portsPerCard;
1319        }
1320        else
1321        {
1322            if (!testNode.isOutputCard(outCardNum)) {
1323             statusText1.setText(Bundle.getMessage("DiagnosticError6"));
1324             return;
1325            }
1326            begOutByte = (testNode.getOutputCardIndex(outCardNum)) * portsPerCard;
1327        }
1328        // Zero the output buffer
1329        int zero = (invertWriteButton.isSelected()) ? -1:0; 
1330
1331        for (int i=0; i<totalOutBytes; i++)
1332        {
1333         outBytes[i] = (byte) zero;
1334        }
1335
1336        int j=begOutByte;
1337        for (int i=0; i<portsPerCard; i++)
1338        {         
1339         outBytes[j] = (invertWriteButton.isSelected()) ? (byte) ~b[i]: (byte) b[i]; 
1340         j++;
1341        }        
1342        nOutBytes = totalOutBytes;
1343        
1344        SerialMessage m = createOutPacket();
1345        m.setTimeout(50);
1346        _memo.getTrafficController().sendSerialMessage(m, curFrame);
1347    }
1348
1349    /**
1350     * {@inheritDoc}
1351     */
1352    @Override
1353    public void message(SerialMessage m) {
1354    }  // Ignore for now
1355
1356    /**
1357     * Reply notification implementing SerialListener interface
1358     */
1359    @Override
1360    public synchronized void reply(SerialReply l) {
1361        // Test if waiting on this input
1362        if (waitingOnInput && (l.isRcv()) && (testNodeAddr == l.getUA())) {
1363            // This is a receive message for the node being tested
1364            for (int i = begInByte; i <= endInByte; i++) {
1365                // get data bytes, skipping over node address and 'R'
1366                inBytes[i] = (byte) l.getElement(i + 2);
1367            }
1368            replyCount = (l.getNumDataElements()-2);
1369
1370            waitingOnInput = false;
1371        }
1372    }
1373
1374    /**
1375     * Stop operation when window closing
1376     */
1377    @Override
1378    public void windowClosing(java.awt.event.WindowEvent e) {
1379        if (testRunning) {
1380            if (outTest) {
1381                stopOutputTest();
1382            } else if (wrapTest) {
1383                stopWraparoundTest();
1384            }
1385        }
1386        super.windowClosing(e);
1387    }
1388
1389    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DiagnosticFrame.class);
1390}