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}