001package jmri.jmrix.sprog.update;
002
003import static jmri.jmrix.sprog.SprogConstants.TC_BOOT_REPLY_TIMEOUT;
004
005import jmri.jmrix.sprog.SprogConstants.SprogState;
006import jmri.jmrix.sprog.SprogMessage;
007import jmri.jmrix.sprog.SprogSystemConnectionMemo;
008import jmri.util.swing.JmriJOptionPane;
009
010/**
011 * Frame for SPROG II firmware update utility.
012 *
013 * Extended to cover SPROG 3 which uses the same bootloader protocol Refactored
014 *
015 * @author Andrew Crosland Copyright (C) 2004
016 */
017public class SprogIIUpdateFrame
018        extends SprogUpdateFrame
019        implements SprogVersionListener {
020
021    public SprogIIUpdateFrame(SprogSystemConnectionMemo memo) {
022        super(memo);
023    }
024
025    /** 
026     * {@inheritDoc}
027     */
028    @Override
029    public void initComponents() {
030        super.initComponents();
031
032        // add help menu to window
033        addHelpMenu("package.jmri.jmrix.sprog.update.SprogIIUpdateFrame", true);
034
035        // Set a shorter timeout in the TC. Must be shorter than SprogUpdateFrame long timeout
036        tc.setTimeout(TC_BOOT_REPLY_TIMEOUT);
037        
038        // Get the SPROG version
039        _memo.getSprogVersionQuery().requestVersion(this);
040    }
041
042    int bootVer = 0;
043
044    /** 
045     * {@inheritDoc}
046     * @param v SPROG version to be decoded
047     */
048    @Override
049    synchronized public void notifyVersion(SprogVersion v) {
050        sv = v;
051        if (sv!=null && sv.sprogType.isSprog() == false) {
052            // Didn't recognize a SPROG so check if it is in boot mode already
053            log.debug("SPROG not found - looking for bootloader");
054            statusBar.setText(Bundle.getMessage("StatusSprogNotFound"));
055            blockLen = -1;
056            requestBoot();
057        } else {
058            // Check that it's not a V4
059            if (sv!=null && sv.sprogType.sprogType > SprogType.SPROGV4) {
060                statusBar.setText(Bundle.getMessage("StatusFoundX", sv.toString()));
061                blockLen = sv.sprogType.getBlockLen();
062                // Put SPROG in boot mode
063                log.debug("Putting SPROG in boot mode");
064                msg = new SprogMessage("b 1 1 1");
065                bootState = BootState.SETBOOTSENT;
066                tc.sendSprogMessage(msg, this);
067                // SPROG II and 3 will not reply to this if successfull. Will
068                // reply with error if firmware is locked. Wait a while to allow
069                // Traffic Controller to time out
070                startLongTimer();
071            } else {
072                log.error("Incorrect SPROG Type detected");
073                statusBar.setText(Bundle.getMessage("StatusIncorrectSprogType"));
074                bootState = BootState.IDLE;
075            }
076        }
077    }
078
079    @Override
080    synchronized protected void frameCheck() {
081        // If SPROG II is in boot mode, check message framing and checksum
082        if ((bootState != BootState.RESETSENT) && tc.isSIIBootMode() && !reply.strip()) {
083            stopTimer();
084            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogString"),
085                    Bundle.getMessage("ErrorFrameDialogTitle"), JmriJOptionPane.ERROR_MESSAGE);
086            log.error("Malformed bootloader reply");
087            statusBar.setText(Bundle.getMessage("StatusMalformedbootLoaderReply"));
088            bootState = BootState.IDLE;
089            tc.setSprogState(SprogState.NORMAL);
090            return;
091        }
092        if ((bootState != BootState.RESETSENT) && tc.isSIIBootMode() && !reply.getChecksum()) {
093            log.error("Bad bootloader checksum");
094            statusBar.setText(Bundle.getMessage("StatusBadBootloaderChecksum"));
095            bootState = BootState.IDLE;
096            tc.setSprogState(SprogState.NORMAL);
097        }
098    }
099
100    @Override
101    synchronized protected void stateSetBootSent() {
102        stopTimer();
103        log.debug("reply in SETBOOTSENT state");
104        // A reply to the enter bootloader command means the firmware is locked.
105        bootState = BootState.IDLE;
106        tc.setSprogState(SprogState.NORMAL);
107        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFirmwareLocked"),
108                Bundle.getMessage("SprogXFirmwareUpdate"), JmriJOptionPane.ERROR_MESSAGE);
109        statusBar.setText(Bundle.getMessage("ErrorFirmwareLocked"));
110    }
111    
112    @Override
113    synchronized protected void stateBootVerReqSent() {
114        stopTimer();
115        if (log.isDebugEnabled()) {
116            log.debug("reply in VERREQSENT state");
117        }
118        // see if reply is the version
119        if ((reply.getOpCode() == SprogMessage.RD_VER) && (reply.getElement(1) == 2)) {
120            bootVer = reply.getElement(2);
121            if (log.isDebugEnabled()) {
122                log.debug("Found bootloader version {}", bootVer);
123            }
124            statusBar.setText(Bundle.getMessage("StatusConnectedToBootloader", bootVer));
125            // Enable the file chooser button
126            setSprogModeButton.setEnabled(true);
127            openFileChooserButton.setEnabled(true);
128            if (blockLen > 0) {
129                // We think we already know the version
130                if (blockLen != SprogType.getBlockLen(bootVer)) {
131                    log.error("Bootloader version does not match SPROG type");
132                    bootState = BootState.IDLE;
133                }
134            } else {
135                // Don't yet have correct SPROG version
136                if (bootVer <= 11) {
137                    // Force SPROG version SPROG II 1.x or 2.x
138                    sv = new SprogVersion(new SprogType(SprogType.SPROGII), "");
139                } else {
140                    // Force SPROG version SPROG SPROG II v3.x (also covers IIv4, SPROG 3 and Nano)
141                    sv = new SprogVersion(new SprogType(SprogType.SPROGIIv3), "");
142                }
143                blockLen = sv.sprogType.getBlockLen();
144                // We remain in this state until program button is pushed
145            }
146        } else {
147            log.error("Bad reply to RD_VER request");
148            bootState = BootState.IDLE;
149            tc.setSprogState(SprogState.NORMAL);
150            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusUnableToConnectBootloader"),
151                    Bundle.getMessage("SprogXFirmwareUpdate"), JmriJOptionPane.ERROR_MESSAGE);
152            statusBar.setText(Bundle.getMessage("StatusUnableToConnectBootloader"));
153        }
154    }
155
156    @Override
157    synchronized protected void stateWriteSent() {
158        stopTimer();
159        if (log.isDebugEnabled()) {
160            log.debug("reply in WRITESENT state");
161        }
162        // Check for correct response to type of write that was sent
163        if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1)
164                || (reply.getElement(reply.getNumDataElements() - 1) == '.')) {
165            if (hexFile.read() > 0) {
166                // More data to write
167                sendWrite();
168            } else {
169                doneWriting();
170            }
171        } else {
172            // Houston, we have a problem
173//            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyWriteRequest"),
174//                                        Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE);
175            log.error("Bad reply to write request");
176            statusBar.setText(Bundle.getMessage("StatusBadReplyWriteRequest"));
177            bootState = BootState.IDLE;
178            tc.setSprogState(SprogState.NORMAL);
179        }
180    }
181
182    @Override
183    synchronized protected void stateEraseSent() {
184        stopTimer();
185        if (log.isDebugEnabled()) {
186            log.debug("reply in ERASESENT state");
187        }
188        // Check for correct response to erase that was sent
189        if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1)) {
190            // Don't erase ICD debug executive if in use
191            if ((sv.sprogType.sprogType < SprogType.SPROGIIv3) && (eraseAddress < 0x7c00)
192                    || (sv.sprogType.sprogType >= SprogType.SPROGIIv3) && (eraseAddress < 0x3F00)) {
193                // More data to erase
194                sendErase();
195            } else {
196                if (log.isDebugEnabled()) {
197                    log.debug("Finished erasing");
198                }
199                statusBar.setText(Bundle.getMessage("StatusEraseComplete"));
200                // Read first line from hexfile
201                if (hexFile.read() > 0) {
202                    // Program line and wait for reply
203                    if (log.isDebugEnabled()) {
204                        log.debug("First write {} {}", hexFile.getLen(), hexFile.getAddress());
205                    }
206                    sendWrite();
207                } else {
208                    doneWriting();
209                }
210            }
211        } else {
212            // Houston, we have a problem
213//        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyErase"),
214//                                        Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE);
215            log.error("Bad reply to erase request");
216            bootState = BootState.IDLE;
217            tc.setSprogState(SprogState.NORMAL);
218        }
219    }
220
221    @Override
222    synchronized protected void stateSprogModeSent() {
223        stopTimer();
224        if (log.isDebugEnabled()) {
225            log.debug("reply in SROGMODESENT state");
226        }
227        // Check for correct response to type of write that was sent
228        if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1)) {
229            if (log.isDebugEnabled()) {
230                log.debug("Reset SPROG");
231            }
232            msg = SprogMessage.getReset();
233            bootState = BootState.RESETSENT;
234            tc.sendSprogMessage(msg, this);
235            startLongTimer();
236        } else {
237            // Houston, we have a problem
238//        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyModeRequest"),
239//                                        Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE);
240            log.error("Bad reply to SPROG Mode request");
241            bootState = BootState.IDLE;
242            tc.setSprogState(SprogState.NORMAL);
243        }
244    }
245
246    @Override
247    synchronized protected void stateResetSent() {
248        stopTimer();
249        if (log.isDebugEnabled()) {
250            log.debug("reply in RESETSENT state");
251        }
252        // Check for correct response to type of write that was sent
253
254        statusBar.setText(Bundle.getMessage("DefaultStatusText")); // Ready, is in jmrixBundle
255
256        tc.setSprogState(SprogState.NORMAL);
257        bootState = BootState.IDLE;
258    }
259
260    @Override
261    synchronized protected void requestBoot() {
262        // Look for SPROG in boot mode by requesting bootloader version.
263        if (log.isDebugEnabled()) {
264            log.debug("Request bootloader version");
265        }
266        // allow parsing of bootloader replies
267        if (tc == null) {
268            log.warn("requestBoot with null tc, ignored");
269            return;
270        }
271        tc.setSprogState(SprogState.SIIBOOTMODE);
272        bootState = BootState.VERREQSENT;
273        msg = SprogMessage.getReadBootVersion();
274        tc.sendSprogMessage(msg, this);
275        startLongTimer();
276    }
277
278    @Override
279    synchronized protected void sendWrite() {
280        if ((hexFile.getAddressU()&0xFF) >= 0xF0) {
281            // Write to EEPROM
282            if (log.isDebugEnabled()) {
283                log.debug("Send write EE {}", hexFile.getAddress());
284            }
285            msg = SprogMessage.getWriteEE(hexFile.getAddress(), hexFile.getData());
286        } else if ((hexFile.getAddressU()&0xFF) >= 0x20) {
287            // Write to user data or config data not supported
288            if (log.isDebugEnabled()) {
289                log.debug("null write {}", hexFile.getAddress());
290            }
291            msg = null;
292        } else if (sv.sprogType.isValidFlashAddress(hexFile.getAddress())) {
293            // Program code address is above bootloader range and below debug executive
294            if (log.isDebugEnabled()) {
295                log.debug("Send write Flash {}", hexFile.getAddress());
296            }
297            msg = SprogMessage.getWriteFlash(hexFile.getAddress(), hexFile.getData(), blockLen);
298            if (log.isDebugEnabled()) {
299                log.debug("msg: {}", msg.toString(true));
300            }
301        } else {
302            // Do nothing
303            if (log.isDebugEnabled()) {
304                log.debug("null write {}", hexFile.getAddress());
305            }
306            msg = null;
307        }
308        if (msg != null) {
309            bootState = BootState.WRITESENT;
310            statusBar.setText(Bundle.getMessage("StatusWriteX", hexFile.getAddress()));
311            tc.sendSprogMessage(msg, this);
312            if (log.isDebugEnabled()) {
313                log.debug("Sent write command to address {}", hexFile.getAddress());
314            }
315            startLongTimer();
316        } else {
317            // use timeout to kick off the next write
318            bootState = BootState.NULLWRITE;
319            statusBar.setText(Bundle.getMessage("StatusSkipX", hexFile.getAddress()));
320            startVShortTimer();
321        }
322    }
323
324    synchronized private void sendErase() {
325        if (log.isDebugEnabled()) {
326            log.debug("Erase Flash {}", eraseAddress);
327        }
328        int rows = 8; // 512 bytes
329        msg = SprogMessage.getEraseFlash(eraseAddress, rows);
330        bootState = BootState.ERASESENT;
331        statusBar.setText(Bundle.getMessage("StatusEraseX", eraseAddress));
332        tc.sendSprogMessage(msg, this);
333        if (log.isDebugEnabled()) {
334            log.debug("Sent erase command to address {}", eraseAddress);
335        }
336        eraseAddress += (rows * 64);
337        startLongTimer();
338    }
339
340    @Override
341    synchronized protected void doneWriting() {
342        // Finished
343        if (log.isDebugEnabled()) {
344            log.debug("Done writing");
345        }
346        statusBar.setText(Bundle.getMessage("StatusWriteComplete"));
347        openFileChooserButton.setEnabled(false);
348        programButton.setEnabled(false);
349
350        setSprogModeButton.setEnabled(true);
351        bootState = BootState.IDLE;
352    }
353
354    @Override
355    synchronized public void programButtonActionPerformed(java.awt.event.ActionEvent e) {
356        if (hexFile != null) {
357            openFileChooserButton.setEnabled(false);
358            programButton.setEnabled(false);
359            setSprogModeButton.setEnabled(false);
360            eraseAddress = sv.sprogType.getEraseStart();
361            if (eraseAddress > 0) {
362                if (log.isDebugEnabled()) {
363                    log.debug("Start erasing @{}", eraseAddress);
364                }
365                sendErase();
366            }
367        }
368    }
369
370    @Override
371    synchronized public void setSprogModeButtonActionPerformed(java.awt.event.ActionEvent e) {
372        if (log.isDebugEnabled()) {
373            log.debug("Set SPROG mode");
374        }
375        msg = SprogMessage.getWriteEE(0xff, new int[]{0});
376        bootState = BootState.SPROGMODESENT;
377        // Set TC timeout back to normal
378        tc.resetTimeout();
379        tc.sendSprogMessage(msg, this);
380        startLongTimer();
381    }
382
383    /**
384     * Removes SprogVersionListener.
385     * Calls Super to stop Timer.
386     * {@inheritDoc}
387     */
388    @Override
389    public void dispose(){
390        if (_memo !=null) {
391            _memo.getSprogVersionQuery().removeSprogVersionListener(this);
392        }
393        super.dispose();
394    }
395
396    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogIIUpdateFrame.class);
397}