001package jmri.jmrix.nce.cab;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.Dimension;
005import java.awt.GridBagConstraints;
006import java.awt.GridBagLayout;
007import java.awt.event.ActionEvent;
008import java.text.MessageFormat;
009import java.util.Calendar;
010import javax.swing.BoxLayout;
011import javax.swing.JButton;
012import javax.swing.JCheckBox;
013import javax.swing.JComponent;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JScrollPane;
017import javax.swing.JTable;
018import javax.swing.ListSelectionModel;
019import javax.swing.ScrollPaneConstants;
020import javax.swing.table.AbstractTableModel;
021import javax.swing.table.TableCellEditor;
022import javax.swing.table.TableColumn;
023import javax.swing.table.TableColumnModel;
024import jmri.jmrix.nce.NceBinaryCommand;
025import jmri.jmrix.nce.NceCmdStationMemory;
026import jmri.jmrix.nce.NceMessage;
027import jmri.jmrix.nce.NceReply;
028import jmri.jmrix.nce.NceSystemConnectionMemo;
029import jmri.jmrix.nce.NceTrafficController;
030import jmri.util.table.ButtonEditor;
031import jmri.util.table.ButtonRenderer;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Frame to display NCE cabs
037 * <p>
038 * Note, NCE bit layout MSB = bit 7, LSB = bit 0.
039 * <p>
040 * From Jim Scorse at NCE:
041 * <p>
042 * Each cab has a 256 byte "context page" in system RAM These pages start at
043 * 0x8000 in system RAM with Cab 0 at 0x8800, cab 1 at 0x8900, Cab 2 at 0x8a00,
044 * etc. (PH5 0x3C00)
045 * <p>
046 * Below is a list of offsets (in decimal) into the cab context page for useful
047 * memory locations.
048 * <p>
049 * For example if you want to know the current speed of cab address 2's
050 * currently selected loco look at memory location 0x8a00 + 32 dec (0x20). This
051 * will be address 0x8a20.
052 * <p>
053 * <br>
054 * To determine if a cab is active (plugged in at any point this session) you
055 * will need to look at the byte "FLAGS1" (offset 101)
056 * <p>
057 * If bit 1 of FLAGS1 = 1 then the cab has been talked to at least once by the
058 * command station.
059 * <p>
060 * Bits 0 and 7 indicate the type of cab being use this session at this cab
061 * address
062 * <p>
063 * Bit 7,0 = 0,0 Procab or other cab with an LCD display (type A) Bit 7,0 = 0,1
064 * Cab04 other cab without an LCD (type B) Bit 7,0 = 1,0 USB or similar device
065 * (type C) Bit 7,0 = 1,1 AIU or similar device (type D)
066 * <p>
067 * <br>
068 * CAB_BASE EQU 0 ; LCD_TOP_LINE EQU 0 ;16 chars (in ASCII) for top line of LCD
069 * LCD_BOT_LINE EQU 16 ;16 chars (in ASCII) for bottom line of LCD
070 * <p>
071 * CURR_SPEED EQU 32 ;this cab's current speed ADDR_H EQU 33 ;loco address, high
072 * byte ADDR_L EQU 34 ;loco address, low byte FLAGS EQU 35 ;bit 0 - Do not use
073 * ;bit 1 - 1=128 speed mode, 0=28 speed mode ;bit 2 - 1=forward, 0=reverse ;bit
074 * 3 - Do not use ;bit 4 - Do not use ;bit 5 - Do not use ;bit 6 - Do not use
075 * ;bit 7 - 1=rear loco of consist is active address use reverse speeds
076 * <p>
077 * FUNCTION_L EQU 36 ;bit 0 = function 1, 1=on, 0=off ;bit 1 = function 2, 1=on,
078 * 0=off ;bit 2 = function 3, 1=on, 0=off ;bit 3 = function 4, 1=on, 0=off ;bit
079 * 4 = headlight, 1=on, 0=off
080 * <p>
081 * FUNCTION_H EQU 37 ;bit 0 = function 5, 1=on, 0=off ;bit 1 = function 6, 1=on,
082 * 0=off ;bit 2 = function 7, 1=on, 0=off ;bit 3 = function 8, 1=on, 0=off ;bit
083 * 4 = function 9, 1=on, 0=off ;bit 5 = function 10, 1=on, 0=off ;bit 6 =
084 * function 11, 1=on, 0=off ;bit 7 = function 12, 1=on, 0=off
085 * <p>
086 * ALIAS EQU 38 ;If loco is in consist this is the consist address
087 * <p>
088 * <br>
089 * FUNC13_20 EQU 82 ;bit map of current functions (bit 0=F13) FUNC21_28 EQU 83
090 * ;bit map of current functions (bit 0=F21)
091 * <p>
092 * ACC_AD_H EQU 90 ;current accessory address high byte ACC_AD_L EQU 91 ;current
093 * accessory address low byte
094 * <p>
095 * ;lower nibble bit 0 =1 if setup advanced consist in process
096 * <p>
097 * FLAGS2 EQU 93 ;bit 0 = \ ;bit 1 = {@literal >}Number of recalls for this cab
098 * ;bit 2 = / 1-6 valid ;bit 3 = 1=refresh LCD on ProCab ;bit 4 = Do not use
099 * ;bit 5 = Do not use ;bit 6 = Do not use ;bit 7 = Do not use
100 * <p>
101 * FLAGS1 EQU 101 ;bit0 - 0 = type a or type C cab, 1 = type b or type d ;bit1 -
102 * 0 = cab type not determined, 1 = it has ;bit2 - 0 = Do not use ;bit3 - 0 = Do
103 * not use ;bit4 - 0 = Do not use ;bit5 - 0 = Do not use ;bit6 - 0 = Do not use
104 * ;bit7 - 0 = type a or type b cab, 1=type c or d Writing zero to FLAGS1 will
105 * remove the cab from the 'active' list
106 *
107 * @author Dan Boudreau Copyright (C) 2009, 2010
108 * @author Ken Cameron Copyright (C) 2012, 2013, 2023
109 */
110public class NceShowCabPanel extends jmri.jmrix.nce.swing.NcePanel implements jmri.jmrix.nce.NceListener {
111
112    private int replyLen = 0; // expected byte length
113    private int waiting = 0; // to catch responses not
114    // intended for this module
115    private int minCabNum = -1; // either the USB or serial size depending on what we connect to
116    private int maxCabNum = -1; // either the USB or serial size depending on what we connect to
117
118    private static final int FIRST_TIME_SLEEP = 3000; // delay first operation to let panel build
119
120    private static final int CAB_LINE_LEN = 16; // display line length of 16 bytes
121    private static final int CAB_MAX_CABDATA = 66; // Size for arrays. One more than the highest cab number
122
123    Thread nceCabUpdateThread;
124    Thread autoRefreshThread;
125
126    private final int[] cabFlag1Array = new int[CAB_MAX_CABDATA];
127    private final Calendar[] cabLastChangeArray = new Calendar[CAB_MAX_CABDATA];
128    private final int[] cabSpeedArray = new int[CAB_MAX_CABDATA];
129    private final int[] cabFlagsArray = new int[CAB_MAX_CABDATA];
130    private final int[] cabLocoArray = new int[CAB_MAX_CABDATA];
131    private final boolean[] cabLongShortArray = new boolean[CAB_MAX_CABDATA];
132    private final int[] cabConsistArray = new int[CAB_MAX_CABDATA];
133    private final int[] cabF0Array = new int[CAB_MAX_CABDATA];
134    private final int[] cabF5Array = new int[CAB_MAX_CABDATA];
135    private final int[] cabF13Array = new int[CAB_MAX_CABDATA];
136    private final int[] cabF21Array = new int[CAB_MAX_CABDATA];
137    private final int[][] cabLine1Array = new int[CAB_MAX_CABDATA][CAB_LINE_LEN];
138    private final int[][] cabLine2Array = new int[CAB_MAX_CABDATA][CAB_LINE_LEN];
139
140    private boolean purgeRequested = false;
141    private boolean updateRequested = false;
142    private int purgeCabId = -1;
143
144    // member declarations
145    JLabel textNumber = new JLabel(Bundle.getMessage("Number"));
146    JLabel textCab = new JLabel(Bundle.getMessage("Type"));
147    JLabel textAddrType = new JLabel(Bundle.getMessage("AddrType"));
148    JLabel textAddress = new JLabel(Bundle.getMessage("Loco"));
149    JLabel textSpeed = new JLabel(Bundle.getMessage("Speed"));
150    JLabel textConsist = new JLabel(Bundle.getMessage("Consist"));
151    JLabel textConsistPos = new JLabel(Bundle.getMessage("ConsistPos"));
152    JLabel textFunctions = new JLabel(Bundle.getMessage("Functions"));
153    JLabel textDisplay1 = new JLabel(Bundle.getMessage("Display1"));
154    JLabel textDisplay2 = new JLabel(Bundle.getMessage("Display2"));
155    JLabel textReply = new JLabel(Bundle.getMessage("Reply"));
156    JLabel textStatus = new JLabel("");
157    JLabel textLastUsed = new JLabel(Bundle.getMessage("LastUsed"));
158
159    // major buttons
160    JButton refreshButton = new JButton(Bundle.getMessage("Refresh"));
161
162    // check boxes
163    JCheckBox checkBoxShowAllCabs = new JCheckBox(Bundle.getMessage("CheckBoxLabelShowAllCabs"));
164    //    JCheckBox checkBoxShowDisplayText = new JCheckBox(Bundle.getMessage("CheckBoxLabelShowDisplayText"));
165    //    JCheckBox checkBoxShowAllFunctions = new JCheckBox(Bundle.getMessage("CheckBoxLabelShowAllFunctions"));
166    JCheckBox checkBoxAutoRefresh = new JCheckBox(Bundle.getMessage("CheckBoxLabelAutoRefresh"));
167
168    static class DataRow {
169
170        int cabNumber;
171        String cabType;
172        String longShort;
173        int locoAddress;
174        int locoSpeed;
175        String locoDir;
176        String mode;
177        int consist;
178        String consistPos;
179        boolean F0;
180        boolean F1;
181        boolean F2;
182        boolean F3;
183        boolean F4;
184        boolean F5;
185        boolean F6;
186        boolean F7;
187        boolean F8;
188        boolean F9;
189        boolean F10;
190        boolean F11;
191        boolean F12;
192        boolean F13;
193        boolean F14;
194        boolean F15;
195        boolean F16;
196        boolean F17;
197        boolean F18;
198        boolean F19;
199        boolean F20;
200        boolean F21;
201        boolean F22;
202        boolean F23;
203        boolean F24;
204        boolean F25;
205        boolean F26;
206        boolean F27;
207        boolean F28;
208        String text1;
209        String text2;
210        String lastChange;
211    }
212
213    DataRow[] cabData = new DataRow[CAB_MAX_CABDATA];
214
215    NceCabTableModel cabModel = new NceCabTableModel(cabData);
216    JTable cabTable = new JTable(cabModel);
217
218    private NceTrafficController tc = null;
219
220    public NceShowCabPanel() {
221        super();
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public void initContext(Object context) {
229        if (context instanceof NceSystemConnectionMemo) {
230            initComponents((NceSystemConnectionMemo) context);
231        }
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    @Override
238    public String getHelpTarget() {
239        return "package.jmri.jmrix.nce.cab.NceShowCabFrame";
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    public String getTitle() {
247        StringBuilder x = new StringBuilder();
248        if (memo != null) {
249            x.append(memo.getUserName());
250        } else {
251            x.append("NCE_");
252        }
253        x.append(": ");
254        x.append(Bundle.getMessage("Title"));
255        return x.toString();
256    }
257
258    /**
259     * {@inheritDoc}
260     */
261    @Override
262    public void initComponents(NceSystemConnectionMemo m) {
263        this.memo = m;
264        this.tc = m.getNceTrafficController();
265
266        // fill in cab array
267        minCabNum = tc.csm.getCabMin();
268        maxCabNum = tc.csm.getCabMax();
269        for (int i = minCabNum; i <= maxCabNum; i++) {
270            cabData[i] = new DataRow();
271        }
272        // the following code sets the frame's initial state
273
274        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
275
276        JPanel p1 = new JPanel();
277        p1.setLayout(new GridBagLayout());
278        p1.setPreferredSize(new Dimension(1000, 40));
279        // row 1
280        refreshButton.setToolTipText(Bundle.getMessage("RefreshToolTip"));
281        addButtonAction(refreshButton);
282
283        checkBoxShowAllCabs.setToolTipText(Bundle.getMessage("CheckBoxAllCabsToolTip"));
284        checkBoxShowAllCabs.setSelected(false);
285        addCheckBoxAction(checkBoxShowAllCabs);
286
287        //        checkBoxShowAllFunctions.setToolTipText(Bundle.getMessage("CheckBoxShowAllFunctionsToolTip"));
288        //        checkBoxShowAllFunctions.setSelected(true);
289        //        checkBoxShowAllFunctions.setEnabled(false);
290        //        checkBoxShowDisplayText.setToolTipText(Bundle.getMessage("CheckBoxShowDisplayToolTip"));
291        //        checkBoxShowDisplayText.setSelected(true);
292        //        checkBoxShowDisplayText.setEnabled(false);
293        checkBoxAutoRefresh.setToolTipText(Bundle.getMessage("CheckBoxAutoRefreshToolTip"));
294        checkBoxAutoRefresh.setSelected(false);
295        addCheckBoxAction(checkBoxAutoRefresh);
296
297        addItem(p1, refreshButton, 2, 1);
298        addItem(p1, checkBoxAutoRefresh, 3, 1);
299        addItem(p1, checkBoxShowAllCabs, 4, 1);
300        //        addItem(p1, checkBoxShowAllFunctions, 6, 1);
301        addItem(p1, textStatus, 2, 2);
302        //        addItem(p1, checkBoxShowDisplayText, 6, 2);
303
304        JScrollPane cabScrollPane = new JScrollPane(cabTable);
305        cabTable.setFillsViewportHeight(true);
306        cabTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
307        cabModel.setShowAllCabs(false);
308        cabModel.setShowAllFunctions(true);
309        cabModel.setShowCabDisplay(true);
310        for (int col = 0; col < cabTable.getColumnCount(); col++) {
311            int width = cabModel.getPreferredWidth(col);
312            TableColumn c = cabTable.getColumnModel().getColumn(col);
313            c.setPreferredWidth(width);
314        }
315        cabTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
316        cabScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
317        cabScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
318        setColumnPurgeButton(cabTable, 2);
319        add(p1);
320        add(cabScrollPane);
321
322        // pad out panel
323        cabScrollPane.setVisible(true);
324
325        refreshPanel();
326
327    }
328
329    // button actions
330    public void buttonActionPerformed(ActionEvent ae) {
331        Object src = ae.getSource();
332        if (src == refreshButton) {
333            refreshPanel();
334        } else {
335            log.error("unknown action performed: {}", src);
336        }
337    }
338
339    // checkboxes
340    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
341        Object src = ae.getSource();
342        if (src == checkBoxShowAllCabs) {
343            cabModel.setShowAllCabs(checkBoxShowAllCabs.isSelected());
344            refreshPanel();
345        } else if (src == checkBoxAutoRefresh) {
346            autoRefreshPanel();
347        } else {
348            log.error("unknown checkbox action performed: {}", src);
349        }
350    }
351
352    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT",
353                                                        justification="I18N of log message")
354    public void purgeCab(int cab) {
355        if (cab < minCabNum || cab > maxCabNum) {
356            log.error("{}{}", Bundle.getMessage("ErrorValueRange"), cab);
357            return;
358        }
359        // if id is active
360        int act = cabFlag1Array[cab] & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE;
361        if (act != NceCmdStationMemory.FLAGS1_CABISACTIVE) {
362            log.error("purgeCab: {}{}", Bundle.getMessage("ErrorCabNotActive"), cab);
363        }
364        // clear bit for active and cab type details
365        cabFlag1Array[cab] = 0;
366        processMemory(true, true, cab);
367    }
368
369    /**
370     * Refresh cab display every 4 seconds.
371     */
372    private void autoRefreshPanel() {
373        if (checkBoxAutoRefresh.isSelected()) {
374            autoRefreshThread = new Thread(new Runnable() {
375                @Override
376                public void run() {
377                    while (true) {
378                        refreshPanel();
379                        synchronized (this) {
380                            try {
381                                wait(4000); // 4 seconds
382                            } catch (InterruptedException e) {
383                                break;
384                            }
385                        }
386                    }
387                }
388            });
389            autoRefreshThread.setName("NCE Show Cabs Auto Refresh");
390            autoRefreshThread.setPriority(Thread.MIN_PRIORITY);
391            autoRefreshThread.start();
392        } else {
393            autoRefreshThread.interrupt();
394        }
395    }
396
397    private void refreshPanel() {
398        processMemory(false, true, -1);
399    }
400
401    private void processMemory(boolean doPurge, boolean doUpdate, int cabId) {
402        if (doPurge) {
403            purgeRequested = true;
404            purgeCabId = cabId;
405        }
406        if (doUpdate) {
407            updateRequested = true;
408        }
409        // Set up a separate thread to access CS memory
410        if (nceCabUpdateThread != null && nceCabUpdateThread.isAlive()) {
411            return; // thread is already running
412        }
413        textStatus.setText(Bundle.getMessage("StatusProcessingMemory"));
414        nceCabUpdateThread = new Thread(new Runnable() {
415            @Override
416            public void run() {
417                if (tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_NONE) {
418                    if (purgeRequested) {
419                        cabPurgeSerial();
420                    }
421                    if (updateRequested) {
422                        cabUpdateSerial();
423                    }
424                } else {
425                    if (purgeRequested) {
426                        cabPurgeUsb();
427                    }
428                    if (updateRequested) {
429                        cabUpdateUsb();
430                    }
431                }
432            }
433        });
434        nceCabUpdateThread.setName(Bundle.getMessage("ThreadTitle"));
435        nceCabUpdateThread.setPriority(Thread.MIN_PRIORITY);
436        nceCabUpdateThread.start();
437    }
438
439    private boolean firstTime = false; // wait for panel to display
440
441    public void cabPurgeSerial() {
442        if (purgeCabId <= minCabNum || purgeCabId >= maxCabNum) {
443            log.error("purgeCabId out of range: {}", purgeCabId);
444        }
445        if (firstTime) {
446            try {
447                Thread.sleep(FIRST_TIME_SLEEP); // wait for panel to display
448            } catch (InterruptedException e) {
449                log.error("Thread unexpectedly interrupted", e);
450            }
451        }
452
453        firstTime = false;
454        // clear bit for active and cab type details
455        cabFlag1Array[purgeCabId] = 0;
456        writeCabMemory1(purgeCabId, tc.csm.getCabIdxFlag1(), 0);
457        if (!waitNce()) {
458            return;
459        }
460        textStatus.setText(MessageFormat.format(Bundle.getMessage("StatusCabPurged"), purgeCabId));
461    }
462
463    public void cabPurgeUsb() {
464        if (purgeCabId <= minCabNum || purgeCabId >= maxCabNum) {
465            log.error("purgeCabId out of range: {}", purgeCabId);
466        }
467        if (firstTime) {
468            try {
469                Thread.sleep(FIRST_TIME_SLEEP); // wait for panel to display
470            } catch (InterruptedException e) {
471                log.error("Thread unexpectedly interrupted", e);
472            }
473        }
474
475        firstTime = false;
476        // clear bit for active and cab type details
477        cabFlag1Array[purgeCabId] = 0;
478        setUsbCabMemoryPointer(purgeCabId, tc.csm.getCabIdxFlag1());
479        if (!waitNce()) {
480            return;
481        }
482        writeUsbCabMemory1(0);
483        if (!waitNce()) {
484            return;
485        }
486        textStatus.setText(MessageFormat.format(Bundle.getMessage("StatusCabPurged"), purgeCabId));
487    }
488
489    // Thread to update cab info, allows the use of sleep or wait, for serial connection
490    private void cabUpdateSerial() {
491
492        if (firstTime) {
493            try {
494                Thread.sleep(FIRST_TIME_SLEEP); // wait for panel to display
495            } catch (InterruptedException e) {
496                log.error("Thread unexpectedly interrupted", e);
497            }
498        }
499
500        firstTime = false;
501        int cabsFound = 0;
502        // build table of cabs
503        for (int currCabId = minCabNum; currCabId <= maxCabNum; currCabId++) {
504
505            textStatus.setText(MessageFormat.format(Bundle.getMessage("StatusProcessingCabId"), currCabId));
506            cabData[currCabId].cabNumber = currCabId;
507            int foundChange = 0;
508            recChar = -1;
509            // create cab type by reading the FLAGS1 byte
510            readCabMemory1(currCabId, tc.csm.getCabIdxFlag1());
511            if (!waitNce()) {
512                return;
513            }
514            log.debug("ID = {} Read flag1 character {}", currCabId, recChar);
515            // test it really changed
516            if (recChar != -1) {
517                // save value for purge
518                if (recChar != cabFlag1Array[currCabId]) {
519                    foundChange++;
520                    if (log.isDebugEnabled()) {
521                        log.debug("{}: Flag1 {}<->{}", currCabId, recChar, cabFlag1Array[currCabId]);
522                    }
523                }
524                cabFlag1Array[currCabId] = recChar;
525                if ((recChar & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE) != NceCmdStationMemory.FLAGS1_CABISACTIVE) {
526                    // not active slot
527                    continue;
528                }
529                if (currCabId >= 1 || !checkBoxShowAllCabs.isSelected()) {
530                    cabsFound++;
531                }
532                int cabType = recChar & NceCmdStationMemory.FLAGS1_MASK_CABTYPE; // mask off don't care bits
533                if (currCabId == minCabNum) {
534                    cabData[currCabId].cabType = Bundle.getMessage("TypeSerial");
535                } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_DISPLAY) {
536                    cabData[currCabId].cabType = Bundle.getMessage("TypeProCab");
537                } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_NODISP) {
538                    cabData[currCabId].cabType = Bundle.getMessage("TypeCab04"); // Cab04 or Cab06
539                } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
540                    cabData[currCabId].cabType = Bundle.getMessage("TypeUSB"); // USB or Mini-Panel
541                } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_AIU) {
542                    cabData[currCabId].cabType = Bundle.getMessage("TypeAIU");
543                } else {
544                    cabData[currCabId].cabType = Bundle.getMessage("TypeUnknownCab") + ": " + recChar;
545                }
546
547                cabData[currCabId].cabNumber = currCabId;
548                if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_AIU) {
549                    // get the AIU data and map it to the function bits
550                    readAiuData(currCabId);
551                    if (!waitNce()) {
552                        return;
553                    }
554                    processAiuData(currCabId, recChars);
555                    //                 } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
556                    // I don't have anything to do for the USB at this time
557                } else {
558                    // read 16 bytes of memory, we'll use 7 of the 16
559                    readCabMemory16(currCabId, NceCmdStationMemory.CAB_CURR_SPEED);
560                    if (!waitNce()) {
561                        return;
562                    }
563                    // read the Speed byte
564                    int readChar = recChars[0];
565                    if (cabSpeedArray[currCabId] != readChar) {
566                        foundChange++;
567                        if (log.isDebugEnabled()) {
568                            log.debug("{}: Speed {}<->{}", currCabId, readChar, cabSpeedArray[currCabId]);
569                        }
570                    }
571                    cabSpeedArray[currCabId] = readChar;
572                    log.debug("Read speed character {}", readChar);
573                    cabData[currCabId].locoSpeed = readChar;
574
575                    // read the FLAGS byte
576                    readChar = recChars[NceCmdStationMemory.CAB_FLAGS - NceCmdStationMemory.CAB_CURR_SPEED];
577                    if (cabFlagsArray[currCabId] != readChar) {
578                        foundChange++;
579                        if (log.isDebugEnabled()) {
580                            log.debug("{}: Flags {}<->{}", currCabId, readChar, cabFlagsArray[currCabId]);
581                        }
582                    }
583                    cabFlagsArray[currCabId] = readChar;
584                    int direction = readChar & 0x04;
585                    if (direction > 0) {
586                        cabData[currCabId].locoDir = Bundle.getMessage("DirForward");
587                    } else {
588                        cabData[currCabId].locoDir = Bundle.getMessage("DirReverse");
589                    }
590                    int mode = readChar & 0x02;
591                    // USB doesn't use the 28/128 bit
592                    cabData[currCabId].mode = "";
593                    if (cabType != NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
594                        if (mode > 0) {
595                            cabData[currCabId].mode = "128";
596                        } else {
597                            cabData[currCabId].mode = "28";
598                        }
599                    }
600
601                    // create loco address, read the high address byte
602                    readChar = recChars[NceCmdStationMemory.CAB_ADDR_H - NceCmdStationMemory.CAB_CURR_SPEED];
603                    log.debug("Read address high character {}", readChar);
604                    int locoAddress = (readChar & 0x3F) * 256;
605                    boolean aType = ((readChar & 0xC0) == 0xC0);
606                    if (cabLongShortArray[currCabId] != aType) {
607                        foundChange++;
608                        if (log.isDebugEnabled()) {
609                            log.debug("{}: Long {}<->{}", currCabId, aType, cabLongShortArray[currCabId]);
610                        }
611                    }
612                    cabLongShortArray[currCabId] = aType;
613                    if (aType) {
614                        cabData[currCabId].longShort = Bundle.getMessage("IsLongAddr");
615                    } else {
616                        cabData[currCabId].longShort = Bundle.getMessage("IsShortAddr");
617                    }
618                    // read the low address byte
619                    readChar = recChars[NceCmdStationMemory.CAB_ADDR_L - NceCmdStationMemory.CAB_CURR_SPEED];
620                    log.debug("Read address low character {}", readChar);
621                    locoAddress = locoAddress + (readChar & 0xFF);
622                    if (cabLocoArray[currCabId] != locoAddress) {
623                        foundChange++;
624                        if (log.isDebugEnabled()) {
625                            log.debug("{}: Loco {}<->{}", currCabId, locoAddress, cabLocoArray[currCabId]);
626                        }
627                    }
628                    cabLocoArray[currCabId] = locoAddress;
629                    cabData[currCabId].locoAddress = locoAddress;
630
631                    // create consist address
632                    readChar = recChars[NceCmdStationMemory.CAB_ALIAS - NceCmdStationMemory.CAB_CURR_SPEED];
633                    if (cabConsistArray[currCabId] != readChar) {
634                        foundChange++;
635                        if (log.isDebugEnabled()) {
636                            log.debug("{}: Consist {}<->{}", currCabId, readChar, cabConsistArray[currCabId]);
637                        }
638                    }
639                    cabConsistArray[currCabId] = readChar;
640                    cabData[currCabId].consist = readChar;
641
642                    // show consist position if relevant
643                    int pos = cabFlagsArray[currCabId] & NceCmdStationMemory.FLAGS_MASK_CONSIST_REAR;
644                    cabData[currCabId].consistPos = "";
645                    if (cabConsistArray[currCabId] != 0) {
646                        if (pos > 0) {
647                            cabData[currCabId].consistPos = Bundle.getMessage("IsRear");
648                        } else {
649                            cabData[currCabId].consistPos = Bundle.getMessage("IsLead");
650                        }
651                    }
652
653                    // get the functions 0-4 values
654                    readChar = recChars[NceCmdStationMemory.CAB_FUNC_L - NceCmdStationMemory.CAB_CURR_SPEED];
655                    if (cabF0Array[currCabId] != readChar) {
656                        foundChange++;
657                        if (log.isDebugEnabled()) {
658                            log.debug("{}: F0 {}<->{}", currCabId, readChar, cabF0Array[currCabId]);
659                        }
660                    }
661                    cabF0Array[currCabId] = readChar;
662                    log.debug("Function low character {}", readChar);
663                    procFunctions0_4(currCabId, readChar);
664
665                    // get the functions 5-12 values
666                    readChar = recChars[NceCmdStationMemory.CAB_FUNC_H - NceCmdStationMemory.CAB_CURR_SPEED];
667                    if (cabF5Array[currCabId] != readChar) {
668                        foundChange++;
669                        if (log.isDebugEnabled()) {
670                            log.debug("{}: F5 {}<->{}", currCabId, readChar, cabF5Array[currCabId]);
671                        }
672                    }
673                    cabF5Array[currCabId] = readChar;
674                    log.debug("Function high character {}", readChar);
675                    procFunctions5_12(currCabId, readChar);
676
677                    // get the functions 13-20 values
678                    readCabMemory1(currCabId, tc.csm.getCabIdxFunct13_20());
679                    if (!waitNce()) {
680                        return;
681                    }
682                    if (cabF13Array[currCabId] != recChar) {
683                        foundChange++;
684                        if (log.isDebugEnabled()) {
685                            log.debug("{}: F13 {}<->{}", currCabId, recChar, cabF13Array[currCabId]);
686                        }
687                    }
688                    cabF13Array[currCabId] = recChar;
689                    procFunctions13_20(currCabId, recChar);
690
691                    // get the functions 21-28 values
692                    readCabMemory1(currCabId, tc.csm.getCabIdxFunct21_28());
693                    if (!waitNce()) {
694                        return;
695                    }
696                    if (cabF21Array[currCabId] != recChar) {
697                        foundChange++;
698                        if (log.isDebugEnabled()) {
699                            log.debug("{}: F21 {}<->{}", currCabId, recChar, cabF21Array[currCabId]);
700                        }
701                    }
702                    cabF21Array[currCabId] = recChar;
703                    procFunctions21_28(currCabId, recChar);
704
705                    // get the display values
706                    readCabMemory16(currCabId, NceCmdStationMemory.CAB_LINE_1);
707                    if (!waitNce()) {
708                        return;
709                    }
710                    StringBuilder text1 = new StringBuilder();
711                    StringBuilder debug1 = new StringBuilder();
712                    for (int i = 0; i < CAB_LINE_LEN; i++) {
713                        if (cabLine1Array[currCabId][i] != recChars[i]) {
714                            foundChange++;
715                            if (log.isDebugEnabled()) {
716                                log.debug("{}: CabLine1[{}] {}<->{}", currCabId, i, recChars[i], cabLine1Array[currCabId][i]);
717                            }
718                        }
719                        cabLine1Array[currCabId][i] = recChars[i];
720                        if (recChars[i] >= 0x20 && recChars[i] <= 0x7F) {
721                            text1.append((char) recChars[i]);
722                        } else {
723                            text1.append(" ");
724                        }
725                        debug1.append(" ").append(recChars[i]);
726                    }
727                    cabData[currCabId].text1 = text1.toString();
728                    log.debug("TextLine1Debug: {}", debug1);
729
730                    readCabMemory16(currCabId, NceCmdStationMemory.CAB_LINE_2);
731                    if (!waitNce()) {
732                        return;
733                    }
734                    StringBuilder text2 = new StringBuilder();
735                    StringBuilder debug2 = new StringBuilder();
736                    for (int i = 0; i < CAB_LINE_LEN; i++) {
737                        if (cabLine2Array[currCabId][i] != recChars[i]) {
738                            foundChange++;
739                            if (log.isDebugEnabled()) {
740                                log.debug("{}: CabLine2[{}] {}<->{}", currCabId, i, recChars[i], cabLine2Array[currCabId][i]);
741                            }
742                        }
743                        cabLine2Array[currCabId][i] = recChars[i];
744                        if (recChars[i] >= 0x20 && recChars[i] <= 0x7F) {
745                            text2.append((char) recChars[i]);
746                        } else {
747                            text2.append(" ");
748                        }
749                        debug2.append(" ").append(recChars[i]);
750                    }
751                    cabData[currCabId].text2 = text2.toString();
752                    log.debug("TextLine2Debug: {}", debug2);
753
754                    Calendar now = Calendar.getInstance();
755                    if (foundChange > 0 || cabLastChangeArray[currCabId] == null) {
756                        cabLastChangeArray[currCabId] = now;
757                        StringBuilder txt = new StringBuilder();
758                        int h = cabLastChangeArray[currCabId].get(Calendar.HOUR_OF_DAY);
759                        int m = cabLastChangeArray[currCabId].get(Calendar.MINUTE);
760                        int s = cabLastChangeArray[currCabId].get(Calendar.SECOND);
761                        if (h < 10) {
762                            txt.append("0");
763                        }
764                        txt.append(h);
765                        txt.append(":");
766                        if (m < 10) {
767                            txt.append("0");
768                        }
769                        txt.append(m);
770                        txt.append(":");
771                        if (s < 10) {
772                            txt.append("0");
773                        }
774                        txt.append(s);
775                        cabData[currCabId].lastChange = txt.toString();
776                    }
777                }
778            }
779        }
780
781        textStatus.setText(Bundle.getMessage("StatusProcessingDone")
782                + ". "
783                + MessageFormat.format(Bundle.getMessage("StatusCabsFound"), cabsFound));
784        cabModel.fireTableDataChanged();
785        this.setVisible(true);
786        this.repaint();
787    }
788
789    // Thread to update cab info, allows the use of sleep or wait, for NCE-USB connection
790    private void cabUpdateUsb() {
791
792        if (firstTime) {
793            try {
794                Thread.sleep(FIRST_TIME_SLEEP); // wait for panel to display
795            } catch (InterruptedException e) {
796                log.error("Thread unexpectedly interrupted", e);
797            }
798        }
799
800        firstTime = false;
801        int cabsFound = 0;
802        // build table of cabs
803        for (int currCabId = minCabNum; currCabId <= maxCabNum; currCabId++) {
804
805            textStatus.setText(MessageFormat.format(Bundle.getMessage("StatusProcessingCabId"), currCabId));
806            cabData[currCabId].cabNumber = currCabId;
807            int foundChange = 0;
808            recChar = -1;
809            // create cab type by reading the FLAGS1 byte
810            setUsbCabMemoryPointer(currCabId, tc.csm.getCabIdxFlag1());
811            if (!waitNce()) {
812                return;
813            }
814            readUsbCabMemoryN(1);
815            if (!waitNce()) {
816                return;
817            }
818            log.debug("ID = {} Read flag1 character {}", currCabId, recChar);
819            // test it really changed
820            if (recChar != -1) {
821                // save value for purge
822                if (recChar != cabFlag1Array[currCabId]) {
823                    foundChange++;
824                    if (log.isDebugEnabled()) {
825                        log.debug("{}: Flag1 {}<->{}", currCabId, recChar, cabFlag1Array[currCabId]);
826                    }
827                }
828                cabFlag1Array[currCabId] = recChar;
829                if ((recChar & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE) != NceCmdStationMemory.FLAGS1_CABISACTIVE) {
830                    // not active slot
831                    continue;
832                }
833                if (currCabId >= 1 || !checkBoxShowAllCabs.isSelected()) {
834                    cabsFound++;
835                }
836                
837                int cabType = recChar & NceCmdStationMemory.FLAGS1_MASK_CABTYPE; // mask off don't care bits
838                if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_DISPLAY) {
839                    cabData[currCabId].cabType = Bundle.getMessage("TypeProCab");
840                } else if (cabType ==  NceCmdStationMemory.FLAGS1_CABTYPE_NODISP) {
841                    cabData[currCabId].cabType = Bundle.getMessage("TypeCab04"); // Cab04 or Cab06
842                } else if (cabType ==  NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
843                    cabData[currCabId].cabType = Bundle.getMessage("TypeUSB"); // USB or Mini-Panel
844                } else if (cabType ==  NceCmdStationMemory.FLAGS1_CABTYPE_AIU) {
845                    cabData[currCabId].cabType = Bundle.getMessage("TypeAIU");
846                } else {
847                    cabData[currCabId].cabType = Bundle.getMessage("TypeUnknownCab") + ": " + recChar;
848                }
849
850                cabData[currCabId].cabNumber = currCabId;
851                if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_AIU) {
852                    // get the AIU data and map it to the function bits
853                    readAiuData(currCabId);
854                    if (!waitNce()) {
855                        return;
856                    }
857                    processAiuData(currCabId, recChars);
858                    //                 } else if (cabType == NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
859                    // I don't have anything to do for the USB at this time
860                } else {
861                    setUsbCabMemoryPointer(currCabId, NceCmdStationMemory.CAB_CURR_SPEED);
862                    if (!waitNce()) {
863                        return;
864                    }
865                    // read the Speed byte
866                    readUsbCabMemoryN(1);
867                    if (!waitNce()) {
868                        return;
869                    }
870                    int readChar = recChar;
871                    if (cabSpeedArray[currCabId] != readChar) {
872                        foundChange++;
873                        if (log.isDebugEnabled()) {
874                            log.debug("{}: Speed {}<->{}", currCabId, readChar, cabSpeedArray[currCabId]);
875                        }
876                    }
877                    cabSpeedArray[currCabId] = readChar;
878                    log.debug("Read speed character {}", readChar);
879                    cabData[currCabId].locoSpeed = readChar;
880
881                    // create loco address, read the high address byte
882                    readUsbCabMemoryN(1);
883                    if (!waitNce()) {
884                        return;
885                    }
886                    readChar = recChar;
887                    log.debug("Read address high character {}", readChar);
888                    int locoAddress = (readChar & 0x3F) * 256;
889                    boolean aType = ((readChar & 0xC0) == 0xC0);
890                    if (cabLongShortArray[currCabId] != aType) {
891                        foundChange++;
892                        if (log.isDebugEnabled()) {
893                            log.debug("{}: Long {}<->{}", currCabId, aType, cabLongShortArray[currCabId]);
894                        }
895                    }
896                    cabLongShortArray[currCabId] = aType;
897                    if (aType) {
898                        cabData[currCabId].longShort = Bundle.getMessage("IsLongAddr");
899                    } else {
900                        cabData[currCabId].longShort = Bundle.getMessage("IsShortAddr");
901                    }
902                    // read the low address byte
903                    readUsbCabMemoryN(1);
904                    if (!waitNce()) {
905                        return;
906                    }
907                    readChar = recChar;
908                    log.debug("Read address low character {}", readChar);
909                    locoAddress = locoAddress + (readChar & 0xFF);
910                    if (cabLocoArray[currCabId] != locoAddress) {
911                        foundChange++;
912                        if (log.isDebugEnabled()) {
913                            log.debug("{}: Loco {}<->{}", currCabId, locoAddress, cabLocoArray[currCabId]);
914                        }
915                    }
916                    cabLocoArray[currCabId] = locoAddress;
917                    cabData[currCabId].locoAddress = locoAddress;
918
919                    // read the FLAGS byte
920                    readUsbCabMemoryN(1);
921                    if (!waitNce()) {
922                        return;
923                    }
924                    readChar = recChar;
925                    if (cabFlagsArray[currCabId] != readChar) {
926                        foundChange++;
927                        if (log.isDebugEnabled()) {
928                            log.debug("{}: Flags {}<->{}", currCabId, readChar, cabFlagsArray[currCabId]);
929                        }
930                    }
931                    cabFlagsArray[currCabId] = readChar;
932                    int direction = readChar & 0x04;
933                    if (direction > 0) {
934                        cabData[currCabId].locoDir = Bundle.getMessage("DirForward");
935                    } else {
936                        cabData[currCabId].locoDir = Bundle.getMessage("DirReverse");
937                    }
938                    int mode = readChar & 0x02;
939                    // USB doesn't use the 28/128 bit
940                    cabData[currCabId].mode = "";
941                    if (cabType != NceCmdStationMemory.FLAGS1_CABTYPE_USB) {
942                        if (mode > 0) {
943                            cabData[currCabId].mode = "128";
944                        } else {
945                            cabData[currCabId].mode = "28";
946                        }
947                    }
948
949                    // get the functions 0-4 values
950                    readUsbCabMemoryN(1);
951                    if (!waitNce()) {
952                        return;
953                    }
954                    readChar = recChar;
955                    if (cabF0Array[currCabId] != readChar) {
956                        foundChange++;
957                        if (log.isDebugEnabled()) {
958                            log.debug("{}: F0 {}<->{}", currCabId, readChar, cabF0Array[currCabId]);
959                        }
960                    }
961                    cabF0Array[currCabId] = readChar;
962                    if (log.isDebugEnabled()) {
963                        log.debug("Function low character {}", readChar);
964                    }
965                    procFunctions0_4(currCabId, readChar);
966
967                    // get the functions 5-12 values
968                    readUsbCabMemoryN(1);
969                    if (!waitNce()) {
970                        return;
971                    }
972                    readChar = recChar;
973                    if (cabF5Array[currCabId] != readChar) {
974                        foundChange++;
975                        if (log.isDebugEnabled()) {
976                            log.debug("{}: F5 {}<->{}", currCabId, readChar, cabF5Array[currCabId]);
977                        }
978                    }
979                    cabF5Array[currCabId] = readChar;
980                    log.debug("Function high character {}", readChar);
981                    procFunctions5_12(currCabId, readChar);
982
983                    // read consist address
984                    readUsbCabMemoryN(1);
985                    if (!waitNce()) {
986                        return;
987                    }
988                    readChar = recChar;
989                    if (cabConsistArray[currCabId] != readChar) {
990                        foundChange++;
991                        if (log.isDebugEnabled()) {
992                            log.debug("{}: Consist {}<->{}", currCabId, readChar, cabConsistArray[currCabId]);
993                        }
994                    }
995                    cabConsistArray[currCabId] = readChar;
996                    cabData[currCabId].consist = readChar;
997
998                    // show consist position if relevant
999                    int pos = cabFlagsArray[currCabId] & NceCmdStationMemory.FLAGS_MASK_CONSIST_REAR;
1000                    cabData[currCabId].consistPos = "";
1001                    if (cabConsistArray[currCabId] != 0) {
1002                        if (pos > 0) {
1003                            cabData[currCabId].consistPos = Bundle.getMessage("IsRear");
1004                        } else {
1005                            cabData[currCabId].consistPos = Bundle.getMessage("IsLead");
1006                        }
1007                    }
1008
1009                    // get the functions 13-20 values
1010                    setUsbCabMemoryPointer(currCabId, tc.csm.getCabIdxFunct13_20());
1011                    if (!waitNce()) {
1012                        return;
1013                    }
1014                    readUsbCabMemoryN(1);
1015                    if (!waitNce()) {
1016                        return;
1017                    }
1018                    if (cabF13Array[currCabId] != recChar) {
1019                        foundChange++;
1020                        if (log.isDebugEnabled()) {
1021                            log.debug("{}: F13 {}<->{}", currCabId, recChar, cabF13Array[currCabId]);
1022                        }
1023                    }
1024                    cabF13Array[currCabId] = recChar;
1025                    procFunctions13_20(currCabId, recChar);
1026
1027                    // get the functions 20-28 values
1028                    setUsbCabMemoryPointer(currCabId, tc.csm.getCabIdxFunct21_28());
1029                    if (!waitNce()) {
1030                        return;
1031                    }
1032                    readUsbCabMemoryN(1);
1033                    if (!waitNce()) {
1034                        return;
1035                    }
1036                    if (cabF21Array[currCabId] != recChar) {
1037                        foundChange++;
1038                        if (log.isDebugEnabled()) {
1039                            log.debug("{}: F21 {}<->{}", currCabId, recChar, cabF21Array[currCabId]);
1040                        }
1041                    }
1042                    cabF21Array[currCabId] = recChar;
1043                    procFunctions21_28(currCabId, recChar);
1044
1045                    // get the display values
1046                    setUsbCabMemoryPointer(currCabId, NceCmdStationMemory.CAB_LINE_1);
1047                    if (!waitNce()) {
1048                        return;
1049                    }
1050                    readUsbCabMemoryN(4);
1051                    if (!waitNce()) {
1052                        return;
1053                    }
1054                    StringBuilder text1 = new StringBuilder();
1055                    StringBuilder debug1 = new StringBuilder();
1056                    int ptrData;
1057                    int ptrCabLine = 0;
1058                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1059                        if (cabLine1Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1060                            foundChange++;
1061                            if (log.isDebugEnabled()) {
1062                                log.debug("{}: CabLine1[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine1Array[currCabId][ptrCabLine]);
1063                            }
1064                        }
1065                        cabLine1Array[currCabId][ptrCabLine] = recChars[ptrData];
1066                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1067                            text1.append((char) recChars[ptrData]);
1068                        } else {
1069                            text1.append(" ");
1070                        }
1071                        debug1.append(" ").append(recChars[ptrData]);
1072                    }
1073                    readUsbCabMemoryN(4);
1074                    if (!waitNce()) {
1075                        return;
1076                    }
1077                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1078                        if (cabLine1Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1079                            foundChange++;
1080                            if (log.isDebugEnabled()) {
1081                                log.debug("{}: CabLine1[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine1Array[currCabId][ptrCabLine]);
1082                            }
1083                        }
1084                        cabLine1Array[currCabId][ptrCabLine] = recChars[ptrData];
1085                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1086                            text1.append((char) recChars[ptrData]);
1087                        } else {
1088                            text1.append(" ");
1089                        }
1090                        debug1.append(" ").append(recChars[ptrData]);
1091                    }
1092                    readUsbCabMemoryN(4);
1093                    if (!waitNce()) {
1094                        return;
1095                    }
1096                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1097                        if (cabLine1Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1098                            foundChange++;
1099                            if (log.isDebugEnabled()) {
1100                                log.debug("{}: CabLine1[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine1Array[currCabId][ptrCabLine]);
1101                            }
1102                        }
1103                        cabLine1Array[currCabId][ptrCabLine] = recChars[ptrData];
1104                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1105                            text1.append((char) recChars[ptrData]);
1106                        } else {
1107                            text1.append(" ");
1108                        }
1109                        debug1.append(" ").append(recChars[ptrData]);
1110                    }
1111                    readUsbCabMemoryN(4);
1112                    if (!waitNce()) {
1113                        return;
1114                    }
1115                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1116                        if (cabLine1Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1117                            foundChange++;
1118                            if (log.isDebugEnabled()) {
1119                                log.debug("{}: CabLine1[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine1Array[currCabId][ptrCabLine]);
1120                            }
1121                        }
1122                        cabLine1Array[currCabId][ptrCabLine] = recChars[ptrData];
1123                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1124                            text1.append((char) recChars[ptrData]);
1125                        } else {
1126                            text1.append(" ");
1127                        }
1128                        debug1.append(" ").append(recChars[ptrData]);
1129                    }
1130                    cabData[currCabId].text1 = text1.toString();
1131                    log.debug("TextLine1Debug: {}", debug1);
1132
1133                    readUsbCabMemoryN(4);
1134                    if (!waitNce()) {
1135                        return;
1136                    }
1137                    StringBuilder text2 = new StringBuilder();
1138                    StringBuilder debug2 = new StringBuilder();
1139                    ptrCabLine = 0;
1140                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1141                        if (cabLine2Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1142                            foundChange++;
1143                            if (log.isDebugEnabled()) {
1144                                log.debug("{}: CabLine2[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine2Array[currCabId][ptrCabLine]);
1145                            }
1146                        }
1147                        cabLine2Array[currCabId][ptrCabLine] = recChars[ptrData];
1148                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1149                            text2.append((char) recChars[ptrData]);
1150                        } else {
1151                            text2.append(" ");
1152                        }
1153                        debug2.append(" ").append(recChars[ptrData]);
1154                    }
1155                    readUsbCabMemoryN(4);
1156                    if (!waitNce()) {
1157                        return;
1158                    }
1159                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1160                        if (cabLine2Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1161                            foundChange++;
1162                            if (log.isDebugEnabled()) {
1163                                log.debug("{}: CabLine2[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine2Array[currCabId][ptrCabLine]);
1164                            }
1165                        }
1166                        cabLine2Array[currCabId][ptrCabLine] = recChars[ptrData];
1167                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1168                            text2.append((char) recChars[ptrData]);
1169                        } else {
1170                            text2.append(" ");
1171                        }
1172                        debug2.append(" ").append(recChars[ptrData]);
1173                    }
1174                    readUsbCabMemoryN(4);
1175                    if (!waitNce()) {
1176                        return;
1177                    }
1178                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1179                        if (cabLine2Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1180                            foundChange++;
1181                            if (log.isDebugEnabled()) {
1182                                log.debug("{}: CabLine2[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine2Array[currCabId][ptrCabLine]);
1183                            }
1184                        }
1185                        cabLine2Array[currCabId][ptrCabLine] = recChars[ptrData];
1186                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1187                            text2.append((char) recChars[ptrData]);
1188                        } else {
1189                            text2.append(" ");
1190                        }
1191                        debug2.append(" ").append(recChars[ptrData]);
1192                    }
1193                    readUsbCabMemoryN(4);
1194                    if (!waitNce()) {
1195                        return;
1196                    }
1197                    for (ptrData = 0; ptrData < 4; ptrData++, ptrCabLine++) {
1198                        if (cabLine2Array[currCabId][ptrCabLine] != recChars[ptrData]) {
1199                            foundChange++;
1200                            if (log.isDebugEnabled()) {
1201                                log.debug("{}: CabLine2[{}] {}<->{}", currCabId, ptrCabLine, recChars[ptrData], cabLine2Array[currCabId][ptrCabLine]);
1202                            }
1203                        }
1204                        cabLine2Array[currCabId][ptrCabLine] = recChars[ptrData];
1205                        if (recChars[ptrData] >= 0x20 && recChars[ptrData] <= 0x7F) {
1206                            text2.append((char) recChars[ptrData]);
1207                        } else {
1208                            text2.append(" ");
1209                        }
1210                        debug2.append(" ").append(recChars[ptrData]);
1211                    }
1212                    cabData[currCabId].text2 = text2.toString();
1213                    log.debug("TextLine2Debug: {}", debug2);
1214
1215                    // add log time stamp
1216                    Calendar now = Calendar.getInstance();
1217                    if (foundChange > 0 || cabLastChangeArray[currCabId] == null) {
1218                        cabLastChangeArray[currCabId] = now;
1219                        StringBuilder txt = new StringBuilder();
1220                        int h = cabLastChangeArray[currCabId].get(Calendar.HOUR_OF_DAY);
1221                        int m = cabLastChangeArray[currCabId].get(Calendar.MINUTE);
1222                        int s = cabLastChangeArray[currCabId].get(Calendar.SECOND);
1223                        if (h < 10) {
1224                            txt.append("0");
1225                        }
1226                        txt.append(h);
1227                        txt.append(":");
1228                        if (m < 10) {
1229                            txt.append("0");
1230                        }
1231                        txt.append(m);
1232                        txt.append(":");
1233                        if (s < 10) {
1234                            txt.append("0");
1235                        }
1236                        txt.append(s);
1237                        cabData[currCabId].lastChange = txt.toString();
1238                    }
1239                }
1240            }
1241        }
1242
1243        textStatus.setText(Bundle.getMessage("StatusProcessingDone")
1244                + ". "
1245                + MessageFormat.format(Bundle.getMessage("StatusCabsFound"), cabsFound));
1246        cabModel.fireTableDataChanged();
1247        this.setVisible(true);
1248        this.repaint();
1249    }
1250
1251    /**
1252     * Process for functions F0-F4.
1253     * <p>
1254     */
1255    private void procFunctions0_4(int currCabId, int c) {
1256        cabData[currCabId].F0 = (c & NceCmdStationMemory.FUNC_L_F0) != 0;
1257        cabData[currCabId].F1 = (c & NceCmdStationMemory.FUNC_L_F1) != 0;
1258        cabData[currCabId].F2 = (c & NceCmdStationMemory.FUNC_L_F2) != 0;
1259        cabData[currCabId].F3 = (c & NceCmdStationMemory.FUNC_L_F3) != 0;
1260        cabData[currCabId].F4 = (c & NceCmdStationMemory.FUNC_L_F4) != 0;
1261    }
1262
1263    /**
1264     * Process for functions 5 through 12.
1265     * <p>
1266     */
1267    private void procFunctions5_12(int currCabId, int c) {
1268        cabData[currCabId].F5 = (c & NceCmdStationMemory.FUNC_H_F5) != 0;
1269        cabData[currCabId].F6 = (c & NceCmdStationMemory.FUNC_H_F6) != 0;
1270        cabData[currCabId].F7 = (c & NceCmdStationMemory.FUNC_H_F7) != 0;
1271        cabData[currCabId].F8 = (c & NceCmdStationMemory.FUNC_H_F8) != 0;
1272        cabData[currCabId].F9 = (c & NceCmdStationMemory.FUNC_H_F9) != 0;
1273        cabData[currCabId].F10 = (c & NceCmdStationMemory.FUNC_H_F10) != 0;
1274        cabData[currCabId].F11 = (c & NceCmdStationMemory.FUNC_H_F11) != 0;
1275        cabData[currCabId].F12 = (c & NceCmdStationMemory.FUNC_H_F12) != 0;
1276    }
1277
1278    /**
1279     * Process char for functions 13-20.
1280     * <p>
1281     */
1282    private void procFunctions13_20(int currCabId, int c) {
1283        cabData[currCabId].F13 = (c & NceCmdStationMemory.FUNC_H_F13) != 0;
1284        cabData[currCabId].F14 = (c & NceCmdStationMemory.FUNC_H_F14) != 0;
1285        cabData[currCabId].F15 = (c & NceCmdStationMemory.FUNC_H_F15) != 0;
1286        cabData[currCabId].F16 = (c & NceCmdStationMemory.FUNC_H_F16) != 0;
1287        cabData[currCabId].F17 = (c & NceCmdStationMemory.FUNC_H_F17) != 0;
1288        cabData[currCabId].F18 = (c & NceCmdStationMemory.FUNC_H_F18) != 0;
1289        cabData[currCabId].F19 = (c & NceCmdStationMemory.FUNC_H_F19) != 0;
1290        cabData[currCabId].F20 = (c & NceCmdStationMemory.FUNC_H_F20) != 0;
1291    }
1292
1293    /**
1294     * Process char for functions 21-28.
1295     * <p>
1296     */
1297    private void procFunctions21_28(int currCabId, int c) {
1298        cabData[currCabId].F21 = (c & NceCmdStationMemory.FUNC_H_F21) != 0;
1299        cabData[currCabId].F22 = (c & NceCmdStationMemory.FUNC_H_F22) != 0;
1300        cabData[currCabId].F23 = (c & NceCmdStationMemory.FUNC_H_F23) != 0;
1301        cabData[currCabId].F24 = (c & NceCmdStationMemory.FUNC_H_F24) != 0;
1302        cabData[currCabId].F25 = (c & NceCmdStationMemory.FUNC_H_F25) != 0;
1303        cabData[currCabId].F26 = (c & NceCmdStationMemory.FUNC_H_F26) != 0;
1304        cabData[currCabId].F27 = (c & NceCmdStationMemory.FUNC_H_F27) != 0;
1305        cabData[currCabId].F28 = (c & NceCmdStationMemory.FUNC_H_F28) != 0;
1306    }
1307
1308    private void processAiuData(int currCabId, int[] ptr) {
1309        cabData[currCabId].F1 = (ptr[1] & 0x01) == 0;
1310        cabData[currCabId].F2 = (ptr[1] & 0x02) == 0;
1311        cabData[currCabId].F3 = (ptr[1] & 0x04) == 0;
1312        cabData[currCabId].F4 = (ptr[1] & 0x08) == 0;
1313        cabData[currCabId].F5 = (ptr[1] & 0x10) == 0;
1314        cabData[currCabId].F6 = (ptr[1] & 0x20) == 0;
1315        cabData[currCabId].F7 = (ptr[1] & 0x40) == 0;
1316        cabData[currCabId].F8 = (ptr[1] & 0x80) == 0;
1317        cabData[currCabId].F9 = (ptr[0] & 0x01) == 0;
1318        cabData[currCabId].F10 = (ptr[0] & 0x02) == 0;
1319        cabData[currCabId].F11 = (ptr[0] & 0x04) == 0;
1320        cabData[currCabId].F12 = (ptr[0] & 0x08) == 0;
1321        cabData[currCabId].F13 = (ptr[0] & 0x10) == 0;
1322        cabData[currCabId].F14 = (ptr[0] & 0x20) == 0;
1323    }
1324
1325    // puts the thread to sleep while we wait for the read CS memory to complete
1326    private boolean waitNce() {
1327        int count = 100;
1328        log.debug("Going to sleep");
1329        while (waiting > 0) {
1330            synchronized (this) {
1331                try {
1332                    wait(100);
1333                } catch (InterruptedException e) {
1334                    //nothing to see here, move along
1335                }
1336            }
1337            count--;
1338            if (count < 0) {
1339                textStatus.setText(Bundle.getMessage("ErrorTitle"));
1340                return false;
1341            }
1342        }
1343        log.debug("awake!");
1344        return true;
1345    }
1346
1347    @Override
1348    public void message(NceMessage m) {
1349    } // ignore replies
1350
1351    // response from read
1352    int recChar = 0;
1353    int[] recChars = new int[16];
1354
1355    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "Thread wait from main transfer loop")
1356    @Override
1357    public void reply(NceReply r) {
1358        log.debug("Receive character");
1359        if (waiting <= 0) {
1360            log.error("unexpected response. Len: {} code: {}", r.getNumDataElements(), r.getElement(0));
1361            return;
1362        }
1363        waiting--;
1364        if (r.getNumDataElements() != replyLen) {
1365            textStatus.setText(Bundle.getMessage("ErrorTitle"));
1366            return;
1367        }
1368        // Read one byte
1369        if (replyLen == NceMessage.REPLY_1) {
1370            // Looking for proper response
1371            recChar = r.getElement(0);
1372        }
1373        // Read two byte
1374        if (replyLen == NceMessage.REPLY_2) {
1375            // Looking for proper response
1376            for (int i = 0; i < NceMessage.REPLY_2; i++) {
1377                recChars[i] = r.getElement(i);
1378            }
1379        }
1380        // Read four byte
1381        if (replyLen == NceMessage.REPLY_4) {
1382            // Looking for proper response
1383            for (int i = 0; i < NceMessage.REPLY_4; i++) {
1384                recChars[i] = r.getElement(i);
1385            }
1386        }
1387        // Read 16 bytes
1388        if (replyLen == NceMessage.REPLY_16) {
1389            // Looking for proper response
1390            for (int i = 0; i < NceMessage.REPLY_16; i++) {
1391                recChars[i] = r.getElement(i);
1392            }
1393        }
1394        // wake up thread
1395        synchronized (this) {
1396            notify();
1397        }
1398    }
1399
1400    // Write 1 byte of NCE cab memory
1401    private void writeCabMemory1(int cabNum, int offset, int value) {
1402        int nceCabAddr = getNceCabAddr(cabNum, offset);
1403        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1404        waiting++;
1405        byte[] bl = NceBinaryCommand.accMemoryWrite1(nceCabAddr, (byte) value);
1406        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1407        tc.sendNceMessage(m, this);
1408    }
1409
1410    // Reads 1 byte of NCE cab memory
1411    private void readCabMemory1(int cabNum, int offset) {
1412        int nceCabAddr = getNceCabAddr(cabNum, offset);
1413        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1414        waiting++;
1415        byte[] bl = NceBinaryCommand.accMemoryRead1(nceCabAddr);
1416        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1417        tc.sendNceMessage(m, this);
1418    }
1419
1420    // Reads 16 bytes of NCE cab memory
1421    private void readCabMemory16(int cabNum, int offset) {
1422        int nceCabAddr = getNceCabAddr(cabNum, offset);
1423        replyLen = NceMessage.REPLY_16; // Expect 16 byte response
1424        waiting++;
1425        byte[] bl = NceBinaryCommand.accMemoryRead(nceCabAddr);
1426        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16);
1427        tc.sendNceMessage(m, this);
1428    }
1429
1430    // get address from cab id and offset
1431    private int getNceCabAddr(int cabNum, int offset) {
1432        int nceCabAddr;
1433        if (cabNum <= maxCabNum) {
1434            nceCabAddr = (cabNum * tc.csm.getCabSize()) + tc.csm.getCabAddr() + offset;
1435        } else {
1436            nceCabAddr = tc.csm.getCabAddr() + offset;
1437        }
1438        return nceCabAddr;
1439    }
1440
1441    // USB set cab memory pointer
1442    private void setUsbCabMemoryPointer(int cab, int offset) {
1443        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1444        waiting++;
1445        byte[] bl = NceBinaryCommand.usbMemoryPointer(cab, offset);
1446        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1447        tc.sendNceMessage(m, this);
1448    }
1449
1450    // USB Read N bytes of NCE cab memory
1451    private void readUsbCabMemoryN(int num) {
1452        switch (num) {
1453            case 1:
1454                replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1455                break;
1456            case 2:
1457                replyLen = NceMessage.REPLY_2; // Expect 2 byte response
1458                break;
1459            case 4:
1460                replyLen = NceMessage.REPLY_4; // Expect 4 byte response
1461                break;
1462            default:
1463                log.error("Invalid usb read byte count");
1464                return;
1465        }
1466        waiting++;
1467        byte[] bl = NceBinaryCommand.usbMemoryRead((byte) num);
1468        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
1469        tc.sendNceMessage(m, this);
1470    }
1471
1472    // USB Write 1 byte of NCE cab memory
1473    private void writeUsbCabMemory1(int value) {
1474        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1475        waiting++;
1476        byte[] bl = NceBinaryCommand.usbMemoryWrite1((byte) value);
1477        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1478        tc.sendNceMessage(m, this);
1479    }
1480
1481    // USB Read AIU
1482    private void readAiuData(int cabId) {
1483        replyLen = NceMessage.REPLY_2; // Expect 2 byte response
1484        waiting++;
1485        byte[] bl = NceBinaryCommand.accAiu2Read(cabId);
1486        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
1487        tc.sendNceMessage(m, this);
1488    }
1489
1490    protected void addItem(JPanel p, JComponent c, int x, int y) {
1491        GridBagConstraints gc = new GridBagConstraints();
1492        gc.gridx = x;
1493        gc.gridy = y;
1494        gc.weightx = 100.0;
1495        gc.weighty = 100.0;
1496        p.add(c, gc);
1497    }
1498
1499    protected void addItemLeft(JPanel p, JComponent c, int x, int y) {
1500        GridBagConstraints gc = new GridBagConstraints();
1501        gc.gridx = x;
1502        gc.gridy = y;
1503        gc.weightx = 100.0;
1504        gc.weighty = 100.0;
1505        gc.anchor = GridBagConstraints.WEST;
1506        p.add(c, gc);
1507    }
1508
1509    protected void addItemTop(JPanel p, JComponent c, int x, int y) {
1510        GridBagConstraints gc = new GridBagConstraints();
1511        gc.gridx = x;
1512        gc.gridy = y;
1513        gc.weightx = 100.0;
1514        gc.weighty = 100.0;
1515        gc.anchor = GridBagConstraints.NORTH;
1516        p.add(c, gc);
1517    }
1518
1519    private void addButtonAction(JButton b) {
1520        b.addActionListener(new java.awt.event.ActionListener() {
1521            @Override
1522            public void actionPerformed(java.awt.event.ActionEvent e) {
1523                buttonActionPerformed(e);
1524            }
1525        });
1526    }
1527
1528    private void addCheckBoxAction(JCheckBox b) {
1529        b.addActionListener(new java.awt.event.ActionListener() {
1530            @Override
1531            public void actionPerformed(java.awt.event.ActionEvent e) {
1532                checkBoxActionPerformed(e);
1533            }
1534        });
1535    }
1536
1537    void setColumnPurgeButton(JTable table, int column) {
1538        TableColumnModel tcm = table.getColumnModel();
1539        // install the button renderers & editors in this column
1540        ButtonRenderer buttonRenderer = new ButtonRenderer();
1541        tcm.getColumn(column).setCellRenderer(buttonRenderer);
1542        TableCellEditor buttonEditor = new ButtonEditor(new JButton(Bundle.getMessage("ButtonPurgeCab")));
1543        tcm.getColumn(column).setCellEditor(buttonEditor);
1544        // ensure the table rows, columns have enough room for buttons
1545        table.setRowHeight(new JButton("  " + cabModel.getValueAt(1, column)).getPreferredSize().height);
1546        table.getColumnModel().getColumn(column)
1547                .setPreferredWidth(new JButton(Bundle.getMessage("ButtonPurgeCab")).getPreferredSize().width + 1);
1548    }
1549
1550    @Override
1551    public void dispose() {
1552        cabModel = null;
1553        cabData = null;
1554        if (autoRefreshThread != null) {
1555            autoRefreshThread.interrupt();
1556        }
1557        super.dispose();
1558    }
1559
1560    class NceCabTableModel extends AbstractTableModel {
1561
1562        DataRow[] cabData;
1563
1564        NceCabTableModel(DataRow[] cabDataPtr) {
1565            this.cabData = cabDataPtr;
1566        }
1567
1568        private final String[] columnNames1LineText = {
1569            Bundle.getMessage("ColHeaderCabId"),
1570            Bundle.getMessage("ColHeaderType"),
1571            Bundle.getMessage("ColHeaderPurge"),
1572            Bundle.getMessage("ColHeaderLongShort"),
1573            Bundle.getMessage("ColHeaderLoco"),
1574            Bundle.getMessage("ColHeaderSpeed"),
1575            Bundle.getMessage("ColHeaderDir"),
1576            Bundle.getMessage("ColHeaderMode"),
1577            Bundle.getMessage("ColHeaderConsist"),
1578            Bundle.getMessage("ColHeaderConsistPos"),
1579            Bundle.getMessage("ColHeaderF0"),
1580            Bundle.getMessage("ColHeaderF1"),
1581            Bundle.getMessage("ColHeaderF2"),
1582            Bundle.getMessage("ColHeaderF3"),
1583            Bundle.getMessage("ColHeaderF4"),
1584            Bundle.getMessage("ColHeaderF5"),
1585            Bundle.getMessage("ColHeaderF6"),
1586            Bundle.getMessage("ColHeaderF7"),
1587            Bundle.getMessage("ColHeaderF8"),
1588            Bundle.getMessage("ColHeaderF9"),
1589            Bundle.getMessage("ColHeaderF10"),
1590            Bundle.getMessage("ColHeaderF11"),
1591            Bundle.getMessage("ColHeaderF12"),
1592            Bundle.getMessage("ColHeaderF13"),
1593            Bundle.getMessage("ColHeaderF14"),
1594            Bundle.getMessage("ColHeaderF15"),
1595            Bundle.getMessage("ColHeaderF16"),
1596            Bundle.getMessage("ColHeaderF17"),
1597            Bundle.getMessage("ColHeaderF18"),
1598            Bundle.getMessage("ColHeaderF19"),
1599            Bundle.getMessage("ColHeaderF20"),
1600            Bundle.getMessage("ColHeaderF21"),
1601            Bundle.getMessage("ColHeaderF22"),
1602            Bundle.getMessage("ColHeaderF23"),
1603            Bundle.getMessage("ColHeaderF24"),
1604            Bundle.getMessage("ColHeaderF25"),
1605            Bundle.getMessage("ColHeaderF26"),
1606            Bundle.getMessage("ColHeaderF27"),
1607            Bundle.getMessage("ColHeaderF28"),
1608            Bundle.getMessage("ColHeaderText1"),
1609            Bundle.getMessage("ColHeaderText2"),
1610            Bundle.getMessage("ColHeaderLastUsed")
1611        };
1612
1613        private boolean showAllCabs = false;
1614        private boolean showAllFunctions = false;
1615        private boolean showCabDisplay = false;
1616
1617        @Override
1618        public int getColumnCount() {
1619            return columnNames1LineText.length;
1620        }
1621
1622        @Override
1623        public int getRowCount() {
1624            int activeRows = 0;
1625            if (!getShowAllCabs()) {
1626                for (int i = minCabNum; i <= maxCabNum; i++) {
1627                    if ((cabFlag1Array[i]
1628                            & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE) == NceCmdStationMemory.FLAGS1_CABISACTIVE) {
1629                        activeRows++;
1630                    }
1631                }
1632            } else {
1633                activeRows = maxCabNum - minCabNum + 1;
1634            }
1635            return activeRows;
1636        }
1637
1638        /**
1639         * Return cabId for row number.
1640         *
1641         * @param row row for cab information
1642         * @return cab id
1643         */
1644        protected int getCabIdForRow(int row) {
1645            int activeRows = -1;
1646            if (!getShowAllCabs()) {
1647                for (int i = minCabNum; i <= maxCabNum; i++) {
1648                    if ((cabFlag1Array[i]
1649                            & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE) == NceCmdStationMemory.FLAGS1_CABISACTIVE) {
1650                        activeRows++;
1651                        if (row == activeRows) {
1652                            return i;
1653                        }
1654                    }
1655                }
1656                return -1;
1657            } else {
1658                return row + minCabNum;
1659            }
1660        }
1661
1662        @Override
1663        public String getColumnName(int col) {
1664            return columnNames1LineText[col];
1665        }
1666
1667        @Override
1668        public Object getValueAt(int row, int col) {
1669            int cabId = getCabIdForRow(row);
1670            if (cabId == -1 && !getShowAllCabs()) {
1671                return null; // no active rows
1672            }
1673            if (cabId < minCabNum || cabId > maxCabNum) {
1674                log.error("getCabIdForRow({}) returned {}", row, cabId);
1675                return null;
1676            }
1677            DataRow r = cabData[cabId];
1678            boolean activeCab = (cabFlag1Array[cabId]
1679                    & NceCmdStationMemory.FLAGS1_MASK_CABISACTIVE) == NceCmdStationMemory.FLAGS1_CABISACTIVE;
1680            if (r == null) {
1681                return null;
1682            }
1683            if (!activeCab && (col != 0)) {
1684                return null;
1685            }
1686            switch (col) {
1687                case 0:
1688                    return r.cabNumber;
1689                case 1:
1690                    return r.cabType;
1691                case 2:
1692                    return Bundle.getMessage("ButtonPurgeCab");
1693                case 3:
1694                    return r.longShort;
1695                case 4:
1696                    return r.locoAddress;
1697                case 5:
1698                    return r.locoSpeed;
1699                case 6:
1700                    return r.locoDir;
1701                case 7:
1702                    return r.mode;
1703                case 8:
1704                    return r.consist;
1705                case 9:
1706                    return r.consistPos;
1707                case 10:
1708                    return r.F0;
1709                case 11:
1710                    return r.F1;
1711                case 12:
1712                    return r.F2;
1713                case 13:
1714                    return r.F3;
1715                case 14:
1716                    return r.F4;
1717                case 15:
1718                    return r.F5;
1719                case 16:
1720                    return r.F6;
1721                case 17:
1722                    return r.F7;
1723                case 18:
1724                    return r.F8;
1725                case 19:
1726                    return r.F9;
1727                case 20:
1728                    return r.F10;
1729                case 21:
1730                    return r.F11;
1731                case 22:
1732                    return r.F12;
1733                case 23:
1734                    return r.F13;
1735                case 24:
1736                    return r.F14;
1737                case 25:
1738                    return r.F15;
1739                case 26:
1740                    return r.F16;
1741                case 27:
1742                    return r.F17;
1743                case 28:
1744                    return r.F18;
1745                case 29:
1746                    return r.F19;
1747                case 30:
1748                    return r.F20;
1749                case 31:
1750                    return r.F21;
1751                case 32:
1752                    return r.F22;
1753                case 33:
1754                    return r.F23;
1755                case 34:
1756                    return r.F24;
1757                case 35:
1758                    return r.F25;
1759                case 36:
1760                    return r.F26;
1761                case 37:
1762                    return r.F27;
1763                case 38:
1764                    return r.F28;
1765                case 39:
1766                    return r.text1;
1767                case 40:
1768                    return r.text2;
1769                case 41:
1770                    return r.lastChange;
1771                default:
1772                    log.error("Unhandled column number: {}", col);
1773                    break;
1774            }
1775            return null;
1776        }
1777
1778        @Override
1779        public void setValueAt(Object value, int row, int col) {
1780            int cabId = getCabIdForRow(row);
1781            if (col == 2) {
1782                purgeCab(cabId);
1783            }
1784        }
1785
1786        @Override
1787        public Class<?> getColumnClass(int c) {
1788            if (c == 0 || c == 4 || c == 5 || c == 8) {
1789                return Integer.class;
1790            } else if (c == 1 || c == 3 || c == 6 || c == 7 || c == 9 || (c >= 39 && c <= 41)) {
1791                return String.class;
1792            } else if (c >= 10 && c <= 38) {
1793                return Boolean.class;
1794            } else if (c == 2) {
1795                return JButton.class;
1796            } else {
1797                return null;
1798            }
1799        }
1800
1801        public int getPreferredWidth(int col) {
1802            int width = new JLabel(columnNames1LineText[col]).getPreferredSize().width + 10;
1803            //            log.debug("Column {} width {} name {}", col, width, columnNames1LineText[col]);
1804            return width;
1805        }
1806
1807        @Override
1808        public boolean isCellEditable(int row, int col) {
1809            return col == 2;
1810        }
1811
1812        public boolean getShowAllCabs() {
1813            return this.showAllCabs;
1814        }
1815
1816        public void setShowAllCabs(boolean b) {
1817            this.showAllCabs = b;
1818        }
1819
1820        public boolean getShowAllFunctions() {
1821            return this.showAllFunctions;
1822        }
1823
1824        public void setShowAllFunctions(boolean b) {
1825            this.showAllFunctions = b;
1826        }
1827
1828        public boolean getShowCabDisplay() {
1829            return this.showCabDisplay;
1830        }
1831
1832        public void setShowCabDisplay(boolean b) {
1833            this.showCabDisplay = b;
1834        }
1835
1836    }
1837
1838    /**
1839     * Nested class to create one of these using old-style defaults.
1840     */
1841    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
1842
1843        public Default() {
1844            super("Open NCE Cabs Monitor",
1845                    new jmri.util.swing.sdi.JmriJFrameInterface(),
1846                    NceShowCabPanel.class.getName(),
1847                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
1848        }
1849    }
1850
1851    private final static Logger log = LoggerFactory.getLogger(NceShowCabPanel.class);
1852}