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