001package jmri.jmrix.sprog.update;
002
003import javax.swing.BoxLayout;
004import javax.swing.JButton;
005import javax.swing.JFileChooser;
006import javax.swing.JLabel;
007import javax.swing.JOptionPane;
008import javax.swing.JPanel;
009import javax.swing.SwingConstants;
010import jmri.jmrix.sprog.SprogConstants.SprogState;
011import jmri.jmrix.sprog.SprogListener;
012import jmri.jmrix.sprog.SprogMessage;
013import jmri.jmrix.sprog.SprogReply;
014import jmri.jmrix.sprog.SprogSystemConnectionMemo;
015import jmri.jmrix.sprog.SprogTrafficController;
016import jmri.util.FileUtil;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Frame for SPROG firmware update utility.
022 *
023 * Refactored
024 *
025 * @author Andrew Crosland Copyright (C) 2004
026 * @author Andrew Berridge - Feb 2010 - removed implementation of SprogListener - wasn't
027 * being used.
028 */
029abstract public class SprogUpdateFrame
030        extends jmri.util.JmriJFrame
031        implements SprogListener {
032
033// member declarations
034    protected JButton programButton = new JButton();
035    protected JButton openFileChooserButton = new JButton();
036    protected JButton setSprogModeButton = new JButton();
037
038    protected SprogVersion sv;
039
040    // to find and remember the hex file
041    final javax.swing.JFileChooser hexFileChooser = new JFileChooser(FileUtil.getUserFilesPath());
042
043    JLabel statusBar = new JLabel();
044
045    // File to hold name of hex file
046    transient SprogHexFile hexFile = null;
047
048    SprogMessage msg;
049
050    // members for handling the bootloader interface
051    protected enum BootState {
052
053        IDLE,
054        CRSENT, // awaiting reply to " "
055        QUERYSENT, // awaiting reply to "?"
056        SETBOOTSENT, // awaiting reply from bootloader
057        VERREQSENT, // awaiting reply to version request
058        WRITESENT, // write flash command sent, waiting reply
059        NULLWRITE, // no write sent
060        ERASESENT, // erase sent
061        SPROGMODESENT, // enable sprog mode sent
062        RESETSENT, // reset sent
063        EOFSENT, // v4 end of file sent
064        V4RESET,          // wait for v4 to reset
065    }
066    protected BootState bootState = BootState.IDLE;
067    protected int eraseAddress;
068
069    static final boolean UNKNOWN = false;
070    static final boolean KNOWN = true;
071
072    protected SprogReply reply;
073    protected String replyString;
074    int blockLen = 0;
075
076    protected SprogTrafficController tc = null;
077    protected SprogSystemConnectionMemo _memo = null;
078
079    public SprogUpdateFrame(SprogSystemConnectionMemo memo) {
080        super();
081        _memo = memo;
082    }
083
084    protected String title() {
085        return Bundle.getMessage("SprogXFirmwareUpdate");
086    }
087
088    protected void init() {
089        // connect to the TrafficManager
090        tc = _memo.getSprogTrafficController();
091        tc.setSprogState(SprogState.NORMAL);
092    }
093
094    /**
095     * Stops Timer.
096     * {@inheritDoc}
097     */
098    @Override
099    public void dispose() {
100        stopTimer();
101        tc = null;
102        _memo = null;
103        super.dispose();
104    }
105
106    /** 
107     * {@inheritDoc}
108     */
109    @Override
110    public void initComponents() {
111        // the following code sets the frame's initial state
112        programButton.setText(Bundle.getMessage("ButtonProgram"));
113        programButton.setVisible(true);
114        programButton.setEnabled(false);
115        programButton.setToolTipText(Bundle.getMessage("ButtonProgramTooltip"));
116
117        openFileChooserButton.setText(Bundle.getMessage("ButtonSelectHexFile"));
118        openFileChooserButton.setVisible(true);
119        openFileChooserButton.setEnabled(false);
120        openFileChooserButton.setToolTipText(Bundle.getMessage("ButtonSelectHexFileTooltip"));
121
122        setSprogModeButton.setText(Bundle.getMessage("ButtonSetSPROGMode"));
123        setSprogModeButton.setVisible(true);
124        setSprogModeButton.setEnabled(false);
125        setSprogModeButton.setToolTipText(Bundle.getMessage("ButtonSetSPROGModeTooltip"));
126
127        statusBar.setVisible(true);
128        statusBar.setText(" ");
129        statusBar.setHorizontalTextPosition(SwingConstants.LEFT);
130
131        setTitle(title());
132        getContentPane().setLayout(new BoxLayout(getContentPane(),
133                BoxLayout.Y_AXIS));
134
135        JPanel paneA = new JPanel();
136        paneA.setLayout(new BoxLayout(paneA, BoxLayout.Y_AXIS));
137
138        JPanel buttons1 = new JPanel();
139        buttons1.setLayout(new BoxLayout(buttons1, BoxLayout.X_AXIS));
140        buttons1.add(openFileChooserButton);
141        buttons1.add(programButton);
142
143        JPanel buttons2 = new JPanel();
144        buttons2.setLayout(new BoxLayout(buttons2, BoxLayout.X_AXIS));
145        buttons2.add(setSprogModeButton);
146
147        JPanel status = new JPanel();
148        status.setLayout(new BoxLayout(status, BoxLayout.X_AXIS));
149        status.add(statusBar);
150
151        paneA.add(buttons1);
152        paneA.add(buttons2);
153        paneA.add(status);
154
155        getContentPane().add(paneA);
156
157        openFileChooserButton.addActionListener((java.awt.event.ActionEvent e) -> {
158            openFileChooserButtonActionPerformed(e);
159        });
160
161        programButton.addActionListener((java.awt.event.ActionEvent e) -> {
162            programButtonActionPerformed(e);
163        });
164
165        setSprogModeButton.addActionListener((java.awt.event.ActionEvent e) -> {
166            setSprogModeButtonActionPerformed(e);
167        });
168
169        // connect to data source
170        init();
171
172        // Don't connect to help here, let the subclasses do it
173        // prevent button areas from expanding
174        pack();
175        paneA.setMaximumSize(paneA.getSize());
176//        pack();
177    }
178
179    @Override
180    public void notifyMessage(SprogMessage m) {
181    }
182
183    /**
184     * State machine to catch replies that calls functions to handle each state.
185     * <p>
186     * These functions can be overridden for each SPROG type.
187     *
188     * @param m the SprogReply received from the SPROG
189     */
190    @Override
191    synchronized public void notifyReply(SprogReply m) {
192        reply = m;
193        frameCheck();
194        replyString = m.toString();
195        switch (bootState) {
196            case IDLE:
197                stateIdle();
198                break;
199            case SETBOOTSENT:           // awaiting reply from bootloader
200                stateSetBootSent();
201                break;
202            case VERREQSENT:            // awaiting reply to version request
203                stateBootVerReqSent();
204                break;
205            case WRITESENT:             // write flash command sent, waiting reply
206                stateWriteSent();
207                break;
208            case ERASESENT:             // erase sent
209                stateEraseSent();
210                break;
211            case SPROGMODESENT:         // enable sprog mode sent
212                stateSprogModeSent();
213                break;
214            case RESETSENT:             // reset sent
215                stateResetSent();
216                break;
217            case EOFSENT:               // v4 end of file sent
218                stateEofSent();
219                break;
220            case V4RESET:               // wait for v4 to reset
221                stateV4Reset();
222                break;
223            default:
224                stateDefault();
225                break;
226        }
227    }
228
229    protected void frameCheck() {
230    }
231
232    protected void stateIdle() {
233        if (log.isDebugEnabled()) {
234            log.debug("reply in IDLE state");
235        }
236    }
237
238    protected void stateSetBootSent() {
239    }
240
241    protected void stateBootVerReqSent() {
242    }
243
244    protected void stateWriteSent() {
245    }
246
247    protected void stateEraseSent() {
248    }
249
250    protected void stateSprogModeSent() {
251    }
252
253    protected void stateResetSent() {
254    }
255
256    protected void stateEofSent() {
257    }
258
259    protected void stateV4Reset() {
260    }
261
262    synchronized protected void stateDefault() {
263        // Houston, we have a problem
264        if (log.isDebugEnabled()) {
265            log.debug("Reply in unknown state");
266        }
267        bootState = BootState.IDLE;
268        tc.setSprogState(SprogState.NORMAL);
269    }
270
271    // Normally this happens well before the transfer thread
272    // is kicked off, but it's synchronized anyway to control
273    // access to shared hexFile variable.
274    synchronized public void openFileChooserButtonActionPerformed(java.awt.event.ActionEvent e) {
275        // start at current file, show dialog
276        int retVal = hexFileChooser.showOpenDialog(this);
277
278        // handle selection or cancel
279        if (retVal == JFileChooser.APPROVE_OPTION) {
280            hexFile = new SprogHexFile(hexFileChooser.getSelectedFile().getPath());
281            if (log.isDebugEnabled()) {
282                log.debug("hex file chosen: {}", hexFile.getName());
283            }
284            if ((!hexFile.getName().contains("sprog"))) {
285                JOptionPane.showMessageDialog(this, Bundle.getMessage("HexFileSelectDialogString"),
286                        Bundle.getMessage("HexFileSelectTitle"), JOptionPane.ERROR_MESSAGE);
287                hexFile = null;
288            } else {
289                hexFile.openRd();
290                programButton.setEnabled(true);
291            }
292        }
293    }
294
295    public synchronized void programButtonActionPerformed(java.awt.event.ActionEvent e) {
296    }
297
298    public void setSprogModeButtonActionPerformed(java.awt.event.ActionEvent e) {
299    }
300
301    abstract protected void requestBoot();
302
303    abstract protected void sendWrite();
304
305    abstract protected void doneWriting();
306
307    /**
308     * Internal routine to handle a timeout.
309     */
310    synchronized protected void timeout() {
311        if ((bootState == BootState.CRSENT) || (bootState == BootState.SETBOOTSENT)) {
312            log.debug("timeout in CRSENT - assuming boot mode");
313            // Either:
314            // 1) We were looking for a SPROG in normal mode but have had no reply
315            // so maybe it was already in boot mode.
316            // 2) We sent the b command and had an extected timeout
317            // In both cases, try looking for bootloader version
318            requestBoot();
319        } else if (bootState == BootState.VERREQSENT) {
320            log.error("timeout in VERREQSENT!");
321            JOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorConnectingDialogString"),
322                    Bundle.getMessage("FatalErrorTitle"), JOptionPane.ERROR_MESSAGE);
323            statusBar.setText(Bundle.getMessage("ErrorConnectingStatus"));
324            bootState = BootState.IDLE;
325            tc.setSprogState(SprogState.NORMAL);
326        } else if (bootState == BootState.WRITESENT) {
327            log.error("timeout in WRITESENT!");
328            // This is fatal!
329            JOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorTimeoutDialogString"),
330                    Bundle.getMessage("FatalErrorTitle"), JOptionPane.ERROR_MESSAGE);
331            statusBar.setText(Bundle.getMessage("ErrorTimeoutStatus"));
332            bootState = BootState.IDLE;
333            tc.setSprogState(SprogState.NORMAL);
334        } else if (bootState == BootState.NULLWRITE) {
335            if (hexFile.read() > 0) {
336                // More data to write
337                sendWrite();
338            } else {
339                doneWriting();
340            }
341        }
342    }
343
344    protected int V_SHORT_TIMEOUT = 5;
345    protected int SHORT_TIMEOUT = 500;
346    protected int LONG_TIMEOUT = 4000;
347
348    javax.swing.Timer timer = null;
349
350    /**
351     * Internal routine to start very short timer for null writes.
352     */
353    protected void startVShortTimer() {
354        restartTimer(V_SHORT_TIMEOUT);
355    }
356
357    /**
358     * Internal routine to start timer to protect the mode-change.
359     */
360    protected void startShortTimer() {
361        restartTimer(SHORT_TIMEOUT);
362    }
363
364    /**
365     * Internal routine to restart timer with a long delay.
366     */
367    synchronized protected void startLongTimer() {
368        restartTimer(LONG_TIMEOUT);
369    }
370
371    /**
372     * Internal routine to stop timer, as all is well.
373     */
374    synchronized void stopTimer() {
375        if (timer != null) {
376            timer.stop();
377        }
378    }
379
380    /**
381     * Internal routine to handle timer starts and restarts.
382     * 
383     * @param delay milliseconds until action
384     */
385    synchronized protected void restartTimer(int delay) {
386        if (timer == null) {
387            timer = new javax.swing.Timer(delay, (java.awt.event.ActionEvent e) -> {
388                timeout();
389            });
390        }
391        timer.stop();
392        timer.setInitialDelay(delay);
393        timer.setRepeats(false);
394        timer.start();
395    }
396
397    private final static Logger log = LoggerFactory
398            .getLogger(SprogUpdateFrame.class);
399}