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