001package jmri.jmrix.loconet.downloader;
002
003import java.awt.Color;
004import java.awt.event.ActionEvent;
005import java.awt.event.FocusEvent;
006import java.awt.event.FocusListener;
007
008import javax.swing.BoxLayout;
009import javax.swing.ButtonGroup;
010import javax.swing.JFileChooser;
011import javax.swing.JLabel;
012import javax.swing.JPanel;
013import javax.swing.JRadioButton;
014import javax.swing.JSeparator;
015import javax.swing.JTextField;
016
017import jmri.jmrit.MemoryContents;
018import jmri.jmrix.loconet.LnConstants;
019import jmri.jmrix.loconet.LocoNetMessage;
020import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * Pane for downloading .hex files and .dmf files to those LocoNet devices which
025 * support firmware updates via LocoNet IPL messages.
026 * <p>
027 * This version relies on the file contents interpretation mechanisms built into
028 * the readHex() methods found in class jmri.jmrit.MemoryContents to
029 * automatically interpret the file's addressing type - either 16-bit or 24-bit
030 * addressing. The interpreted addressing type is reported in the pane after a
031 * file is read. The user cannot select the addressing type.
032 * <p>
033 * This version relies on the file contents checking mechanisms built into the
034 * readHex() methods found in class jmri.jmrit.MemoryContents to check for a
035 * wide variety of possible issues in the contents of the firmware update file.
036 * Any exception thrown by at method is used to select an error message to
037 * display in the status line of the pane.
038 *
039 * @author Bob Jacobsen Copyright (C) 2005, 2015
040 * @author B. Milhaupt Copyright (C) 2013, 2014, 2017
041 */
042public class LoaderPane extends jmri.jmrix.AbstractLoaderPane
043        implements jmri.jmrix.loconet.swing.LnPanelInterface {
044
045    /**
046     * LnPanelInterface implementation makes "memo" object available as convenience
047     */
048    protected LocoNetSystemConnectionMemo memo;
049
050    /**
051     * {@inheritDoc}
052     */
053    @Override
054    public void initContext(Object context) {
055        if (context instanceof LocoNetSystemConnectionMemo) {
056            initComponents((LocoNetSystemConnectionMemo) context);
057        }
058    }
059
060    /**
061     * This gets invoked early. We don't want it to do anything, so
062     * we just fail to pass it up. Instead, we wait for the later call of
063     * initComponents(LocoNetSystemConnectionMemo memo)
064     */
065    @Override
066    public void initComponents(){
067    }
068        
069    /**
070     * {@inheritDoc}
071     */
072    @Override
073    public void initComponents(LocoNetSystemConnectionMemo memo) {
074        this.memo = memo; 
075        super.initComponents();
076    }
077
078    /**
079     * LnPanelInterface implementation creates standard form of title.
080     * 
081     * @param menuTitle is a string containing the name of the tool
082     * @return a new string containing the connection's UserName plus the name 
083     *          of the tool
084     */
085    public String getTitle(String menuTitle) { return jmri.jmrix.loconet.swing.LnPanel.getTitleHelper(memo, menuTitle); }
086
087
088    
089    // Local GUI member declarations
090    JTextField bootload = new JTextField();
091    JTextField mfg = new JTextField();
092
093    JTextField developer = new JTextField();
094    JTextField product = new JTextField();
095    JTextField hardware = new JTextField();
096    JTextField software = new JTextField();
097    JTextField delay = new JTextField();
098    JTextField eestart = new JTextField();
099    JTextField eraseBlockSize = new JTextField();
100
101    JRadioButton checkhardwareno = new JRadioButton(Bundle.getMessage("ButtonCheckHardwareNo"));
102    JRadioButton checkhardwareexact = new JRadioButton(Bundle.getMessage("ButtonCheckHardwareExact"));
103    JRadioButton checkhardwaregreater = new JRadioButton(Bundle.getMessage("ButtonCheckHardwareGreater"));
104    ButtonGroup hardgroup = new ButtonGroup();
105
106    JRadioButton checksoftwareno = new JRadioButton(Bundle.getMessage("ButtonCheckSoftwareNo"));
107    JRadioButton checksoftwareless = new JRadioButton(Bundle.getMessage("ButtonCheckSoftwareLess"));
108    ButtonGroup softgroup = new ButtonGroup();
109
110
111    private static final int PXCT1DOWNLOAD = 0x40;
112    static int PXCT2SETUP = 0x00;
113    static int PXCT2SENDADDRESS = 0x10;
114    static int PXCT2SENDDATA = 0x20;
115    static int PXCT2VERIFYDATA = 0x30;
116    static int PXCT2ENDOPERATION = 0x40;
117
118    /*
119     * Flags for "Options".
120     * See {@link http://embeddedloconet.cvs.sourceforge.net/viewvc/embeddedloconet/apps/BootLoader/BootloaderUser.c}
121     */
122    private static final int DO_NOT_CHECK_SOFTWARE_VERSION = 0x00;
123    private static final int CHECK_SOFTWARE_VERSION_LESS = 0x04;
124
125    private static final int DO_NOT_CHECK_HARDWARE_VERSION = 0x00;
126    private static final int REQUIRE_HARDWARE_VERSION_EXACT_MATCH = 0x01;
127    private static final int ACCEPT_LATER_HARDWARE_VERSIONS = 0x03;
128
129    private static final int SW_FLAGS_MSK = 0x04;
130    private static final int HW_FLAGS_MSK = 0x03;
131
132    // some constant string declarations
133    private static final String MIN_VALUE_ZERO = "0"; // NOI18N
134    private static final String MIN_EESTART_VALUE = "8"; // NOI18N
135    private static final String MAX_VALUE_255 = "255"; // NOI18N
136    private static final String MAX_VALUE_65535 = "65535"; // NOI18N
137    private static final String MAX_EESTART_VALUE = "FFFFF8"; // NOI18N
138    private static final String MIN_DELAY_VALUE = "5"; // NOI18N
139    private static final String MAX_DELAY_VALUE = "500"; // NOI18N
140    private static final String MIN_VALUE_64 = "64"; // NOI18N
141    private static final String MAX_VALUE_128 = "128"; // NOI18N
142
143    public LoaderPane() {
144    }
145
146    @Override
147    public String getHelpTarget() {
148        return "package.jmri.jmrix.loconet.downloader.LoaderFrame"; // NOI18N
149    }
150
151    @Override
152    public String getTitle() {
153        return getTitle(Bundle.getMessage("TitleLoader"));
154    }
155
156    @Override
157    protected void addOptionsPanel() {
158        {
159            // create a panel for displaying/modifying the bootloader version
160            JPanel p = new JPanel();
161            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
162            p.add(new JLabel(Bundle.getMessage("LabelBootload") + " "));
163            p.add(bootload);
164            bootload.setToolTipText(Bundle.getMessage("TipValueRange",
165                    MIN_VALUE_ZERO, MAX_VALUE_255));
166            bootload.addFocusListener(new FocusListener() {
167                @Override
168                public void focusGained(FocusEvent e) {
169                }
170
171                @Override
172                public void focusLost(FocusEvent e) {
173                    intParameterIsValid(bootload, 0, 255);
174                    updateDownloadVerifyButtons();
175                }
176            });
177            add(p);
178        }
179
180        {
181            // create a panel for displaying/modifying the manufacturer number
182            JPanel p = new JPanel();
183            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
184            p.add(new JLabel(Bundle.getMessage("LabelMfg") + " "));
185            mfg.setToolTipText(Bundle.getMessage("TipValueRange",
186                    MIN_VALUE_ZERO, MAX_VALUE_255));
187            p.add(mfg);
188            mfg.addFocusListener(new FocusListener() {
189                @Override
190                public void focusGained(FocusEvent e) {
191                }
192
193                @Override
194                public void focusLost(FocusEvent e) {
195                    intParameterIsValid(mfg, 0, 255);
196                    updateDownloadVerifyButtons();
197                }
198            });
199            add(p);
200        }
201
202        {
203            // create a panel for displaying/modifying the developer number
204            JPanel p = new JPanel();
205            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
206            p.add(new JLabel(Bundle.getMessage("LabelDev")
207                    + " ")); // NOI18N
208            developer.setToolTipText(Bundle.getMessage("TipValueRange",
209                    MIN_VALUE_ZERO, MAX_VALUE_255));
210            p.add(developer);
211            developer.addFocusListener(new FocusListener() {
212                @Override
213                public void focusGained(FocusEvent e) {
214                }
215
216                @Override
217                public void focusLost(FocusEvent e) {
218                    intParameterIsValid(developer, 0, 255);
219                    updateDownloadVerifyButtons();
220                }
221            });
222            add(p);
223        }
224
225        {
226            // create a panel for displaying/modifying the product number
227            JPanel p = new JPanel();
228            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
229            p.add(new JLabel(Bundle.getMessage("LabelProduct") + " "));
230            product.setToolTipText(Bundle.getMessage("TipValueRange",
231                    MIN_VALUE_ZERO, MAX_VALUE_65535));
232            p.add(product);
233            product.addFocusListener(new FocusListener() {
234                @Override
235                public void focusGained(FocusEvent e) {
236                }
237
238                @Override
239                public void focusLost(FocusEvent e) {
240                    intParameterIsValid(product, 0, 65535);
241                    updateDownloadVerifyButtons();
242                }
243            });
244
245            add(p);
246        }
247
248        {
249            // create a panel for displaying/modifying the hardware version
250            JPanel p = new JPanel();
251            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
252            hardware.setToolTipText(Bundle.getMessage("TipValueRange",
253                    MIN_VALUE_ZERO, MAX_VALUE_255));
254            p.add(new JLabel(Bundle.getMessage("LabelHardware") + " "));
255            p.add(hardware);
256            hardware.addFocusListener(new FocusListener() {
257                @Override
258                public void focusGained(FocusEvent e) {
259                }
260
261                @Override
262                public void focusLost(FocusEvent e) {
263                    intParameterIsValid(hardware, 0, 255);
264                    updateDownloadVerifyButtons();
265                }
266            });
267
268            add(p);
269        }
270
271        {
272            // create a panel for displaying/modifying the hardware options
273            JPanel p = new JPanel();
274            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
275            p.add(checkhardwareno);
276            p.add(checkhardwareexact);
277            p.add(checkhardwaregreater);
278
279            hardgroup.add(checkhardwareno);
280            hardgroup.add(checkhardwareexact);
281            hardgroup.add(checkhardwaregreater);
282
283//            checkhardwareno.addFocusListener(new FocusListener() {
284//                @Override public void focusGained(FocusEvent e) {
285//                }
286//                @Override public void focusLost(FocusEvent e) {
287//                    updateDownloadVerifyButtons();
288//                }
289//            });
290//            checkhardwareexact.addFocusListener(new FocusListener() {
291//                @Override public void focusGained(FocusEvent e) {
292//                }
293//                @Override public void focusLost(FocusEvent e) {
294//                    updateDownloadVerifyButtons();
295//                }
296//            });
297//            checkhardwaregreater.addFocusListener(new FocusListener() {
298//                @Override public void focusGained(FocusEvent e) {
299//                }
300//                @Override public void focusLost(FocusEvent e) {
301//                    updateDownloadVerifyButtons();
302//                }
303//            });
304            checkhardwareno.addActionListener(this);
305            checkhardwareexact.addActionListener(this);
306            checkhardwaregreater.addActionListener(this);
307            add(p);
308        }
309
310        {
311            // create a panel for displaying/modifying the software version
312            JPanel p = new JPanel();
313            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
314            p.add(new JLabel(Bundle.getMessage("LabelSoftware") + " "));
315            software.setToolTipText(Bundle.getMessage("TipValueRange",
316                    MIN_VALUE_ZERO, MAX_VALUE_255));
317            p.add(software);
318            software.addFocusListener(new FocusListener() {
319                @Override
320                public void focusGained(FocusEvent e) {
321                }
322
323                @Override
324                public void focusLost(FocusEvent e) {
325                    intParameterIsValid(software, 0, 255);
326                    updateDownloadVerifyButtons();
327                }
328            });
329
330            add(p);
331        }
332
333        {
334            // create a panel for displaying/modifying the software options
335            JPanel p = new JPanel();
336            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
337            p.add(checksoftwareno);
338            p.add(checksoftwareless);
339
340            softgroup.add(checksoftwareno);
341            softgroup.add(checksoftwareless);
342
343            checksoftwareno.addActionListener(this);
344            checksoftwareless.addActionListener(this);
345
346            add(p);
347        }
348
349        {
350            // create a panel for displaying/modifying the delay value
351            JPanel p = new JPanel();
352            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
353            p.add(new JLabel(Bundle.getMessage("LabelDelay") + " "));
354            delay.setToolTipText(Bundle.getMessage("TipValueRange",
355                    MIN_DELAY_VALUE, MAX_DELAY_VALUE));
356
357            p.add(delay);
358            delay.addFocusListener(new FocusListener() {
359                @Override
360                public void focusGained(FocusEvent e) {
361                }
362
363                @Override
364                public void focusLost(FocusEvent e) {
365                    intParameterIsValid(hardware,
366                            Integer.parseInt(MIN_DELAY_VALUE),
367                            Integer.parseInt(MAX_DELAY_VALUE));
368                    updateDownloadVerifyButtons();
369                }
370            });
371
372            add(p);
373        }
374
375        {
376            // create a panel for displaying/modifying the EEPROM start address
377            JPanel p = new JPanel();
378            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
379            p.add(new JLabel(Bundle.getMessage("LabelEEStart") + " "));
380            eestart.setToolTipText(Bundle.getMessage("TipValueRange",
381                    MIN_EESTART_VALUE, MAX_EESTART_VALUE));
382            p.add(eestart);
383            eestart.addFocusListener(new FocusListener() {
384                @Override
385                public void focusGained(FocusEvent e) {
386                }
387
388                @Override
389                public void focusLost(FocusEvent e) {
390                    updateDownloadVerifyButtons();
391                }
392            });
393
394            add(p);
395        }
396
397        {
398            // create a panel for displaying/modifying the Erase Block Size
399            JPanel p = new JPanel();
400            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
401            p.add(new JLabel(Bundle.getMessage("LabelEraseBlockSize")
402                    + " ")); // NOI18N
403            eraseBlockSize.setToolTipText(Bundle.getMessage("TipValueRange",
404                    MIN_VALUE_64, MAX_VALUE_128));
405            p.add(eraseBlockSize);
406            eraseBlockSize.addFocusListener(new FocusListener() {
407                @Override
408                public void focusGained(FocusEvent e) {
409                }
410
411                @Override
412                public void focusLost(FocusEvent e) {
413                    if (!intParameterIsValid(eraseBlockSize, 64, 128)) {
414                        status.setText(Bundle.getMessage("ErrorInvalidParameter"));
415                    }
416                    updateDownloadVerifyButtons();
417                }
418            });
419            add(p);
420        }
421        add(new JSeparator());
422
423    }
424
425    @Override
426    protected void handleOptionsInFileContent(MemoryContents inputContent){
427        // get some key/value pairs from the input file (if available)
428        String text = inputContent.extractValueOfKey("Bootloader Version"); // NOI18N
429        if (text != null) {
430            bootload.setText(text);
431        }
432
433        text = inputContent.extractValueOfKey("Manufacturer Code"); // NOI18N
434        if (text != null) {
435            mfg.setText(text);
436        }
437
438        text = inputContent.extractValueOfKey("Developer Code"); // NOI18N
439        if (text != null) {
440            developer.setText(text);
441        }
442
443        text = inputContent.extractValueOfKey("Product Code"); // NOI18N
444        if (text != null) {
445            product.setText(text);
446        }
447
448        text = inputContent.extractValueOfKey("Hardware Version"); // NOI18N
449        if (text != null) {
450            hardware.setText(text);
451        }
452
453        text = inputContent.extractValueOfKey("Software Version"); // NOI18N
454        if (text != null) {
455            software.setText(text);
456        }
457
458        text = inputContent.extractValueOfKey("Options"); // NOI18N
459        if (text != null) {
460            try {
461                this.setOptionsRadiobuttons(text);
462            } catch (NumberFormatException ex) {
463                JmriJOptionPane.showMessageDialog(this,
464                        Bundle.getMessage("ErrorInvalidOptionInFile", text, "Options"), // NOI18N
465                        Bundle.getMessage("ErrorTitle"),
466                        JmriJOptionPane.ERROR_MESSAGE);
467                this.disableDownloadVerifyButtons();
468                log.warn("Invalid dmf file 'Options' value {}",text);
469                return;
470            }
471        }
472
473        text = inputContent.extractValueOfKey("Delay"); // NOI18N
474        if (text != null) {
475            delay.setText(text);
476        }
477
478        text = inputContent.extractValueOfKey("EEPROM Start Address"); // NOI18N
479        if (text != null) {
480            eestart.setText(text);
481        }
482        
483        text = inputContent.extractValueOfKey("Erase Blk Size"); // NOI18N
484        if (text != null) {
485            eraseBlockSize.setText(text);
486            boolean interpretationProblem = false;
487            try {
488                int i = Integer.parseInt(text);
489                if ((i > 128) || (i < 64)) {
490                    interpretationProblem = true;
491                }
492            } catch (java.lang.NumberFormatException e) {
493                interpretationProblem = true;
494            }
495            
496            if (interpretationProblem == true) {
497                log.warn("Invalid dmf file 'Erase Blk Size' value {}",text);
498                JmriJOptionPane.showMessageDialog(this,
499                        Bundle.getMessage("ErrorInvalidEraseBlkSize", text, "Erase Blk Size"), // NOI18N
500                        Bundle.getMessage("ErrorTitle"), // NOI18N
501                        JmriJOptionPane.ERROR_MESSAGE);
502                this.disableDownloadVerifyButtons();
503                // clear out the firmware image to ensure that the user won't 
504                // write it to the device
505                inputContent.clear();
506                setDefaultFieldValues();
507                clearInputFileName();        
508            }
509        }
510    }
511
512    /**
513     * Add filter(s) for possible types to the input file chooser.
514     * @param chooser  a JFileChooser to which the filter is to be added
515     */
516    @Override
517    protected void addChooserFilters(JFileChooser chooser) {
518            javax.swing.filechooser.FileNameExtensionFilter filter
519                    = new javax.swing.filechooser.FileNameExtensionFilter(
520                            Bundle.getMessage("FileFilterLabel", // NOI18N
521                                    "*.dfm, *.hex"), // NOI18N
522                            "dmf", "hex");   // NOI18N
523
524            chooser.addChoosableFileFilter(
525                    new javax.swing.filechooser.FileNameExtensionFilter(
526                            "Digitrax Mangled Firmware (*.dmf)", "dmf")); // NOI18N
527            chooser.addChoosableFileFilter(
528                    new javax.swing.filechooser.FileNameExtensionFilter(
529                            "Intel Hex Format Firmware (*.hex)", "hex")); // NOI18N
530            chooser.addChoosableFileFilter(filter);
531
532            // make the downloadable file filter the default active filter
533            chooser.setFileFilter(filter);
534    }
535
536    private void setOptionsRadiobuttons(String text) throws NumberFormatException {
537        try {
538            int control = Integer.parseInt(text);
539            switch (control & SW_FLAGS_MSK) {
540                case CHECK_SOFTWARE_VERSION_LESS:
541                    checksoftwareless.setSelected(true);
542                    checksoftwareno.setSelected(false);
543                    break;
544                case DO_NOT_CHECK_SOFTWARE_VERSION:
545                    checksoftwareless.setSelected(false);
546                    checksoftwareno.setSelected(true);
547                    break;
548                default:
549                    throw new NumberFormatException("Invalid Software Options: " // NOI18N
550                            + (control & SW_FLAGS_MSK));
551            }
552            switch (control & HW_FLAGS_MSK) {
553                case DO_NOT_CHECK_HARDWARE_VERSION:
554                    checkhardwareno.setSelected(true);
555                    checkhardwareexact.setSelected(false);
556                    checkhardwaregreater.setSelected(false);
557                    break;
558                case REQUIRE_HARDWARE_VERSION_EXACT_MATCH:
559                    checkhardwareno.setSelected(false);
560                    checkhardwareexact.setSelected(true);
561                    checkhardwaregreater.setSelected(false);
562                    break;
563                case ACCEPT_LATER_HARDWARE_VERSIONS:
564                    checkhardwareno.setSelected(false);
565                    checkhardwareexact.setSelected(false);
566                    checkhardwaregreater.setSelected(true);
567                    break;
568                default:
569                    throw new NumberFormatException("Invalid Hardware Options: " // NOI18N
570                            + (control & HW_FLAGS_MSK));
571            }
572        } catch (NumberFormatException ex) {
573            log.error("Invalid Option value: {}", text); // NOI18N
574            throw new NumberFormatException(ex.getLocalizedMessage());
575        }
576    }
577
578
579    @Override
580    protected void doLoad() {
581        super.doLoad();
582
583        // start the download itself
584        operation = PXCT2SENDDATA;
585        sendSequence();
586    }
587
588    @Override
589    protected void doVerify() {
590        super.doVerify();
591
592        // start the download itself
593        operation = PXCT2VERIFYDATA;
594        sendSequence();
595    }
596
597    private int operation;
598
599    private void sendSequence() {
600        int mfgval;
601        int developerval;
602        int prodval;
603        int hardval;
604        int softval;
605        int control;
606
607        // before starting the send sequence, check for bad values in the
608        // GUI text fields containing the parameters.
609        if (!parametersAreValid()) {
610            disableDownloadVerifyButtons();
611            status.setText(Bundle.getMessage("ErrorInvalidParameter"));
612            return;
613        }
614        if (inputContent.isEmpty()) {
615            disableDownloadVerifyButtons();
616            status.setText(Bundle.getMessage("ErrorEmptyFirmwareFile"));
617            return;
618        }
619
620        // now know that the GUI text fields are valid and have some data to move.
621        try {
622            mfgval = Integer.parseInt(mfg.getText());
623            if (mfgval < 0 || mfgval > 0xff) {
624                throw new NumberFormatException("out of range"); // NOI18N
625            }
626        } catch (NumberFormatException ex) {
627            log.error("sendSequence() failed due to bad Manufacturer Number value {}", mfg.getText()); // NOI18N
628            mfg.setForeground(Color.red);
629            mfg.requestFocusInWindow();
630            enableDownloadVerifyButtons();
631            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
632                    Bundle.getMessage("LabelMfg"),
633                    mfg.getText()));
634            return;
635        }
636
637        try {
638            developerval = Integer.parseInt(developer.getText());
639            if (developerval < 0 || developerval > 0xff) {
640                throw new NumberFormatException("out of range"); // NOI18N
641            }
642        } catch (NumberFormatException ex) {
643            log.error("sendSequence() failed due to bad Developer Number value {}", developer.getText()); // NOI18N
644            developer.setForeground(Color.red);
645            developer.requestFocusInWindow();
646            enableDownloadVerifyButtons();
647            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
648                    Bundle.getMessage("LabelDev"),
649                    developer.getText()));
650            return;
651        }
652
653        try {
654            prodval = Integer.parseInt(product.getText());
655            if (prodval < 0 || prodval > 0xffff) {
656                throw new NumberFormatException("out of range"); // NOI18N
657            }
658        } catch (NumberFormatException ex) {
659            log.error("sendSequence() failed due to bad Product Code value {}", product.getText()); // NOI18N
660            product.setForeground(Color.red);
661            product.requestFocusInWindow();
662            this.enableDownloadVerifyButtons();
663            enableDownloadVerifyButtons();
664            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
665                    Bundle.getMessage("LabelProduct"),
666                    product.getText()));
667            return;
668        }
669
670        try {
671            hardval = Integer.parseInt(hardware.getText());
672            if (hardval < 0 || hardval > 0xff) {
673                throw new NumberFormatException("out of range"); // NOI18N
674            }
675        } catch (NumberFormatException ex) {
676            log.error("sendSequence() failed due to bad Hardware Version value {}", hardware.getText()); // NOI18N
677            hardware.setForeground(Color.red);
678            hardware.requestFocusInWindow();
679            enableDownloadVerifyButtons();
680            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
681                    Bundle.getMessage("LabelHardware"),
682                    hardware.getText()));
683            return;
684        }
685
686        try {
687            softval = Integer.parseInt(software.getText());
688            if (softval < 0 || softval > 0xff) {
689                throw new NumberFormatException("out of range"); // NOI18N
690            }
691        } catch (NumberFormatException ex) {
692            log.error("sendSequence() failed due to bad Software Version value {}", software.getText()); // NOI18N
693            software.setForeground(Color.red);
694            software.requestFocusInWindow();
695            enableDownloadVerifyButtons();
696            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
697                    Bundle.getMessage("LabelSoftware"),
698                    software.getText()));
699            return;
700        }
701
702        control = computeOptionsValFromRadiobuttons();
703
704        try {
705            delayval = Integer.parseInt(delay.getText());
706            if ((delayval < Integer.parseInt(MIN_DELAY_VALUE))
707                    || (delayval > Integer.parseInt(MAX_DELAY_VALUE))) {
708                throw new NumberFormatException("out of range"); // NOI18N
709            }
710        } catch (NumberFormatException ex) {
711            log.error("sendSequence() failed due to bad Delay value {}", delay.getText()); // NOI18N
712            delay.setForeground(Color.red);
713            delay.requestFocusInWindow();
714            enableDownloadVerifyButtons();
715            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
716                    Bundle.getMessage("LabelDelay"),
717                    delay.getText()));
718            return;
719        }
720
721        try {
722            eestartval = Integer.parseInt(eestart.getText(), 16);
723            if ((eestartval < Integer.parseInt(MIN_EESTART_VALUE, 16))
724                    || (eestartval > Integer.parseInt(MAX_EESTART_VALUE, 16))) {
725                throw new NumberFormatException("out of range"); // NOI18N
726            }
727        } catch (NumberFormatException ex) {
728            log.error("sendSequence() failed due to bad EESTART value {}", eestart.getText()); // NOI18N
729            eestart.setForeground(Color.red);
730            eestart.requestFocusInWindow();
731            enableDownloadVerifyButtons();
732            status.setText(Bundle.getMessage("ErrorInvalidValueInGUI",
733                    Bundle.getMessage("LabelEEStart"),
734                    eestart.getText()));
735            return;
736        }
737
738        // send start
739        sendOne(PXCT2SETUP, mfgval, prodval & 0xff, hardval, softval,
740                control, 0, developerval, prodval / 256);
741
742        // start transmission loop
743        new Thread(new Sender()).start();
744    }
745
746    void sendOne(int pxct2, int d1, int d2, int d3, int d4,
747            int d5, int d6, int d7, int d8) {
748        LocoNetMessage m = new LocoNetMessage(16);
749        m.setOpCode(LnConstants.OPC_PEER_XFER);
750        m.setElement(1, 0x10);
751        m.setElement(2, 0x7F);
752        m.setElement(3, 0x7F);
753        m.setElement(4, 0x7F);
754
755        int d1u = (d1 & 0x80) / 0x80;
756        int d2u = (d2 & 0x80) / 0x40;
757        int d3u = (d3 & 0x80) / 0x20;
758        int d4u = (d4 & 0x80) / 0x10;
759        int lowbits = d1u | d2u | d3u | d4u;
760
761        m.setElement(5, (lowbits | PXCT1DOWNLOAD) & 0x7F);  // PXCT1
762        m.setElement(6, d1 & 0x7F);  // D1
763        m.setElement(7, d2 & 0x7F);  // D2
764        m.setElement(8, d3 & 0x7F);  // D3
765        m.setElement(9, d4 & 0x7F);  // D4
766
767        int d5u = (d5 & 0x80) / 0x80;
768        int d6u = (d6 & 0x80) / 0x40;
769        int d7u = (d7 & 0x80) / 0x20;
770        int d8u = (d8 & 0x80) / 0x10;
771        lowbits = d5u | d6u | d7u | d8u;
772
773        m.setElement(10, (lowbits | pxct2) & 0x7F);  // PXCT2
774        m.setElement(11, d5 & 0x7F);  // D5
775        m.setElement(12, d6 & 0x7F);  // D6
776        m.setElement(13, d7 & 0x7F);  // D7
777        m.setElement(14, d8 & 0x7F);  // D8
778
779        memo.getLnTrafficController().sendLocoNetMessage(m);
780
781    }
782
783    int startaddr;
784    int endaddr;
785    int delayval;
786    int eestartval;
787
788    private class Sender implements Runnable {
789
790        int totalmsgs;
791        int sentmsgs;
792
793        // send the next data, and a termination record when done
794        @Override
795        public void run() {
796            // define range to be checked for download
797            startaddr = 0x000000;
798            endaddr = 0xFFFFFF;
799
800            if ((startaddr & 0x7) != 0) {
801                log.error("Can only start on an 8-byte boundary: {}", startaddr);
802            }
803
804            // fast scan to count bytes to send
805            int location = inputContent.nextContent(startaddr);
806            totalmsgs = 0;
807            sentmsgs = 0;
808            location = location & 0x00FFFFF8;  // mask off bits to be multiple of 8
809            do {
810                location = location + 8;
811                totalmsgs++;
812                // update to the next location for data
813                int next = inputContent.nextContent(location);
814                if (next < 0) {
815                    break;   // no data left
816                }
817                location = next & 0x00FFFFF8;  // mask off bits to be multiple of 8
818
819            } while (location <= endaddr);
820
821            // find the initial location with data
822            location = inputContent.nextContent(startaddr);
823            if (location < 0) {
824                log.info("No data, which seems odd");
825                return;  // ends load process
826            }
827            location = location & 0x00FFFFF8;  // mask off bits to be multiple of 8
828
829            doLongWait(location, 5);
830            setAddr(location);
831            doLongWait(location, 2);
832
833            do {
834
835                // send this data
836                sentmsgs++;
837                sendOne(operation, // either send or verify
838                        inputContent.getLocation(location++),
839                        inputContent.getLocation(location++),
840                        inputContent.getLocation(location++),
841                        inputContent.getLocation(location++),
842                        inputContent.getLocation(location++),
843                        inputContent.getLocation(location++),
844                        inputContent.getLocation(location++),
845                        inputContent.getLocation(location++));
846
847                // update GUI intermittently
848                if ((sentmsgs % 5) == 0) {
849                    // update progress bar via the queue to ensure synchronization
850                    updateGUI(100 * sentmsgs / totalmsgs);
851                }
852
853                // wait for completion of last operation
854                doWait(location);
855
856                // update to the next location for data
857                int next = inputContent.nextContent(location);
858                if (next < 0) {
859                    break;   // no data left
860                }
861                next = next & 0x00FFFFF8;  // mask off bits to be multiple of 8
862                if ((next != location) || ((location & 0x3f) == 0x00)) {
863                    // wait for completion
864                    doLongWait(next, 4);  // extra wait while device writes memory
865                    // change to next location
866                    setAddr(next);
867                    doLongWait(next, 2); // double wait after sending new address
868                    
869                }
870                location = next;
871
872            } while (!isOperationAborted() && (location <= endaddr));
873
874            // send end (after wait)
875            doLongWait(location,4);
876            sendOne(PXCT2ENDOPERATION, 0, 0, 0, 0, 0, 0, 0, 0);
877
878            this.updateGUI(100); //draw bar to 100%
879
880            // signal end to GUI via the queue to ensure synchronization
881            Runnable r = new Runnable() {
882                @Override
883                public void run() {
884                    enableGUI();
885                }
886            };
887            javax.swing.SwingUtilities.invokeLater(r);
888
889        }
890
891        /**
892         * Send a command to resume at another address
893         * @param location to be written next
894         */
895        void setAddr(int location) {
896            sendOne(PXCT2SENDADDRESS,
897                    (location / 256 / 256) & 0xFF,
898                    (location / 256) & 0xFF,
899                    location & 0xFF,
900                    0, 0, 0, 0, 0);
901        }
902
903        /**
904         * Wait the specified time.
905         *
906         * 16*10/16.44 = 14 msec is the time it takes to send the message.
907         * @param address to be sent next, for computing the delay before 
908         *          sending the next message
909         */
910        void doWait(int address) {
911            try {
912                synchronized (this) {
913                    // make sure enough time in EEPROM address space
914                    int tdelay;
915                    if (address >= eestartval) {
916                        tdelay = delayval + 50 + 14;
917                    } else {
918                        tdelay = delayval + 4 + 14;
919                    }                        
920                    // do the actual wait
921                    wait(tdelay);
922                }
923            } catch (InterruptedException e) {
924                Thread.currentThread().interrupt(); // retain if needed later
925            }
926        }
927
928        /**
929         * Wait the time appropriate for the address.
930         *
931         * @param address to be sent next, for computing the delay before 
932         *          sending the next message
933         */
934        void doLongWait(int address, int multiplier) {
935            try {
936                synchronized (this) {
937                    // make sure enough time in EEPROM address space
938                    int tdelay;
939                    if (address >= eestartval) {
940                        tdelay = (delayval + 50 + 14) * multiplier;
941                    } else {
942                        tdelay = (delayval) * multiplier;
943                    }                        
944                    // do the actual wait
945                    wait(tdelay);
946                }
947            } catch (InterruptedException e) {
948                Thread.currentThread().interrupt(); // retain if needed later
949            }
950        }
951
952        /**
953         * Signal GUI that it's the end of the download
954         * <p>
955         * Should be invoked on the Swing thread
956         */
957        void enableGUI() {
958            LoaderPane.this.enableDownloadVerifyButtons();
959        }
960
961        /**
962         * Update the GUI for progress.
963         *
964         * @param value is the percentage of "doneness" to be displayed
965         */
966        void updateGUI(final int value) {
967            javax.swing.SwingUtilities.invokeLater(new Runnable() {
968                @Override
969                public void run() {
970                    log.debug("updateGUI with {}", value);
971                    // update progress bar
972                    bar.setValue(value);
973                }
974            });
975        }
976
977    }
978
979    @Override
980    protected void setDefaultFieldValues() {
981        addressSizeButtonGroup.clearSelection();
982        bootload.setText("1"); // NOI18N
983        mfg.setText("1"); // NOI18N
984        developer.setText("1"); // NOI18N
985        product.setText("1"); // NOI18N
986        hardware.setText("1"); // NOI18N
987        software.setText("1"); // NOI18N
988        delay.setText("200"); // NOI18N
989        eestart.setText("C00000"); // NOI18N
990        eraseBlockSize.setText("64"); // NOI18N
991        
992        try {
993            setOptionsRadiobuttons(Integer.toString(DO_NOT_CHECK_SOFTWARE_VERSION + REQUIRE_HARDWARE_VERSION_EXACT_MATCH));
994        } catch (NumberFormatException ex) {
995            throw (new java.lang.Error("SetCheckboxes Failed to update the GUI for known-good parameters")); // NOI18N
996        }
997        parametersAreValid();
998    }
999
1000    /**
1001     * Checks the values in the GUI text boxes to determine if any are invalid.
1002     * Intended for use immediately after reading a firmware file for the
1003     * purpose of validating any key/value pairs found in the file. Also
1004     * intended for use immediately before a "verify" or "download" operation to
1005     * check that the user has not changed any of the GUI text values to ones
1006     * that are unsupported.
1007     * <p>
1008     * Note that this method cannot guarantee that the values are suitable for
1009     * the hardware being updated and/or for the particular firmware information
1010     * which was read from the firmware file.
1011     *
1012     * @return false if one or more GUI text box contains an invalid value
1013     */
1014    @Override
1015    protected boolean parametersAreValid() {
1016        boolean allIsOk;
1017        allIsOk = true; // assume that all GUI values are ok.
1018        String text;    // temporary variable to hold text from GUI element
1019        int junk;       // temporary variable to hold interpreted GUI value
1020
1021        boolean temp;
1022        temp = intParameterIsValid(bootload, 0, 255);
1023        allIsOk &= temp;
1024        if (!temp) {
1025            log.info("Bootloader Version Number is not valid: {}", bootload.getText());
1026        }
1027        temp = intParameterIsValid(mfg, 0, 255);
1028        allIsOk &= temp;
1029        if (!temp) {
1030            log.info("Manufacturer Number is not valid: {}", mfg.getText());
1031        }
1032        temp = intParameterIsValid(developer, 0, 255);
1033        allIsOk &= temp;
1034        if (!temp) {
1035            log.info("Developer Number is not valid: {}", bootload.getText());
1036        }
1037        temp = intParameterIsValid(product, 0, 65535);
1038        allIsOk &= temp;
1039        if (!temp) {
1040            log.info("Product Code is not valid: {}", product.getText());
1041        }
1042        temp = intParameterIsValid(hardware, 0, 255);
1043        allIsOk &= temp;
1044        if (!temp) {
1045            log.info("Hardware Version Number is not valid: {}", hardware.getText());
1046        }
1047        temp = intParameterIsValid(software, 0, 255);
1048        allIsOk &= temp;
1049        if (!temp) {
1050            log.info("Software Version Number is not valid: {}", software.getText());
1051        }
1052        temp = intParameterIsValid(delay,
1053                Integer.parseInt(MIN_DELAY_VALUE),
1054                Integer.parseInt(MAX_DELAY_VALUE));
1055        allIsOk &= temp;
1056        if (!temp) {
1057            log.info("Delay is not valid: {}", delay.getText());
1058        }
1059        temp = (hardgroup.getSelection() != null);
1060        allIsOk &= temp;
1061        if (!temp) {
1062            log.info("No harware version check radio button is selected.");
1063        }
1064        temp = (softgroup.getSelection() != null);
1065        allIsOk &= temp;
1066        if (!temp) {
1067            log.info("No software version check radio button is selected.");
1068        }
1069        temp = true;
1070        eestart.setForeground(Color.black);
1071        text = eestart.getText();
1072        if (text.equals("")) {
1073            eestart.setText("0");
1074            eestart.setForeground(Color.red);
1075            temp = false;
1076        } else {
1077            try {
1078                junk = Integer.parseInt(text, 16);
1079            } catch (NumberFormatException ex) {
1080                junk = -1;
1081            }
1082            if ((junk < Integer.parseInt(MIN_EESTART_VALUE, 16))
1083                    || ((junk % 8) != 0)
1084                    || (junk > Integer.parseInt(MAX_EESTART_VALUE, 16))) {
1085                eestart.setForeground(Color.red);
1086                temp = false;
1087            } else {
1088                eestart.setForeground(Color.black);
1089                temp = true;
1090            }
1091        }
1092        eestart.updateUI();
1093        
1094        allIsOk &= temp;
1095
1096        temp = intParameterIsValid(eraseBlockSize, 64, 128);
1097        allIsOk &= temp;
1098        if (!temp) {
1099            log.info("Erase Block Sizez is not valid: {}", eraseBlockSize.getText());
1100        }
1101        
1102        if (allIsOk == true) {
1103            log.debug("No problems found when checking parameter values.");
1104        }
1105
1106        return allIsOk;
1107    }
1108
1109    private int computeOptionsValFromRadiobuttons() {
1110        int control = 0;
1111        if (checksoftwareless.isSelected()) {
1112            control |= CHECK_SOFTWARE_VERSION_LESS;
1113        }
1114
1115        if (checkhardwareexact.isSelected()) {
1116            control |= REQUIRE_HARDWARE_VERSION_EXACT_MATCH;
1117        } else if (checkhardwaregreater.isSelected()) {
1118            control |= ACCEPT_LATER_HARDWARE_VERSIONS;
1119        }
1120        return control;
1121    }
1122
1123    /**
1124     * Conditionally enables or disables the Download and Verify GUI buttons
1125     * based on the validity of the parameter values in the GUI and the state of
1126     * the memory contents object.
1127     */
1128    @Override
1129    protected void updateDownloadVerifyButtons() {
1130        if (parametersAreValid() && !inputContent.isEmpty()) {
1131            enableDownloadVerifyButtons();
1132        } else {
1133            disableDownloadVerifyButtons();
1134        }
1135    }
1136
1137    @Override
1138    public void actionPerformed(ActionEvent e) {
1139        updateDownloadVerifyButtons();
1140        log.info("ActionListener");
1141    }
1142
1143    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoaderPane.class);
1144
1145}