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}