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