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}