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