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