001package jmri.jmrix.sprog.update; 002 003import static jmri.jmrix.sprog.SprogConstants.TC_BOOT_REPLY_TIMEOUT; 004 005import jmri.jmrix.sprog.SprogConstants.SprogState; 006import jmri.jmrix.sprog.SprogMessage; 007import jmri.jmrix.sprog.SprogSystemConnectionMemo; 008import jmri.util.swing.JmriJOptionPane; 009 010/** 011 * Frame for SPROG II firmware update utility. 012 * 013 * Extended to cover SPROG 3 which uses the same bootloader protocol Refactored 014 * 015 * @author Andrew Crosland Copyright (C) 2004 016 */ 017public class SprogIIUpdateFrame 018 extends SprogUpdateFrame 019 implements SprogVersionListener { 020 021 public SprogIIUpdateFrame(SprogSystemConnectionMemo memo) { 022 super(memo); 023 } 024 025 /** 026 * {@inheritDoc} 027 */ 028 @Override 029 public void initComponents() { 030 super.initComponents(); 031 032 // add help menu to window 033 addHelpMenu("package.jmri.jmrix.sprog.update.SprogIIUpdateFrame", true); 034 035 // Set a shorter timeout in the TC. Must be shorter than SprogUpdateFrame long timeout 036 tc.setTimeout(TC_BOOT_REPLY_TIMEOUT); 037 038 // Get the SPROG version 039 _memo.getSprogVersionQuery().requestVersion(this); 040 } 041 042 int bootVer = 0; 043 044 /** 045 * {@inheritDoc} 046 * @param v SPROG version to be decoded 047 */ 048 @Override 049 synchronized public void notifyVersion(SprogVersion v) { 050 sv = v; 051 if (sv!=null && sv.sprogType.isSprog() == false) { 052 // Didn't recognize a SPROG so check if it is in boot mode already 053 log.debug("SPROG not found - looking for bootloader"); 054 statusBar.setText(Bundle.getMessage("StatusSprogNotFound")); 055 blockLen = -1; 056 requestBoot(); 057 } else { 058 // Check that it's not a V4 059 if (sv!=null && sv.sprogType.sprogType > SprogType.SPROGV4) { 060 statusBar.setText(Bundle.getMessage("StatusFoundX", sv.toString())); 061 blockLen = sv.sprogType.getBlockLen(); 062 // Put SPROG in boot mode 063 log.debug("Putting SPROG in boot mode"); 064 msg = new SprogMessage("b 1 1 1"); 065 bootState = BootState.SETBOOTSENT; 066 tc.sendSprogMessage(msg, this); 067 // SPROG II and 3 will not reply to this if successfull. Will 068 // reply with error if firmware is locked. Wait a while to allow 069 // Traffic Controller to time out 070 startLongTimer(); 071 } else { 072 log.error("Incorrect SPROG Type detected"); 073 statusBar.setText(Bundle.getMessage("StatusIncorrectSprogType")); 074 bootState = BootState.IDLE; 075 } 076 } 077 } 078 079 @Override 080 synchronized protected void frameCheck() { 081 // If SPROG II is in boot mode, check message framing and checksum 082 if ((bootState != BootState.RESETSENT) && tc.isSIIBootMode() && !reply.strip()) { 083 stopTimer(); 084 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogString"), 085 Bundle.getMessage("ErrorFrameDialogTitle"), JmriJOptionPane.ERROR_MESSAGE); 086 log.error("Malformed bootloader reply"); 087 statusBar.setText(Bundle.getMessage("StatusMalformedbootLoaderReply")); 088 bootState = BootState.IDLE; 089 tc.setSprogState(SprogState.NORMAL); 090 return; 091 } 092 if ((bootState != BootState.RESETSENT) && tc.isSIIBootMode() && !reply.getChecksum()) { 093 log.error("Bad bootloader checksum"); 094 statusBar.setText(Bundle.getMessage("StatusBadBootloaderChecksum")); 095 bootState = BootState.IDLE; 096 tc.setSprogState(SprogState.NORMAL); 097 } 098 } 099 100 @Override 101 synchronized protected void stateSetBootSent() { 102 stopTimer(); 103 log.debug("reply in SETBOOTSENT state"); 104 // A reply to the enter bootloader command means the firmware is locked. 105 bootState = BootState.IDLE; 106 tc.setSprogState(SprogState.NORMAL); 107 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFirmwareLocked"), 108 Bundle.getMessage("SprogXFirmwareUpdate"), JmriJOptionPane.ERROR_MESSAGE); 109 statusBar.setText(Bundle.getMessage("ErrorFirmwareLocked")); 110 } 111 112 @Override 113 synchronized protected void stateBootVerReqSent() { 114 stopTimer(); 115 if (log.isDebugEnabled()) { 116 log.debug("reply in VERREQSENT state"); 117 } 118 // see if reply is the version 119 if ((reply.getOpCode() == SprogMessage.RD_VER) && (reply.getElement(1) == 2)) { 120 bootVer = reply.getElement(2); 121 if (log.isDebugEnabled()) { 122 log.debug("Found bootloader version {}", bootVer); 123 } 124 statusBar.setText(Bundle.getMessage("StatusConnectedToBootloader", bootVer)); 125 // Enable the file chooser button 126 setSprogModeButton.setEnabled(true); 127 openFileChooserButton.setEnabled(true); 128 if (blockLen > 0) { 129 // We think we already know the version 130 if (blockLen != SprogType.getBlockLen(bootVer)) { 131 log.error("Bootloader version does not match SPROG type"); 132 bootState = BootState.IDLE; 133 } 134 } else { 135 // Don't yet have correct SPROG version 136 if (bootVer <= 11) { 137 // Force SPROG version SPROG II 1.x or 2.x 138 sv = new SprogVersion(new SprogType(SprogType.SPROGII), ""); 139 } else { 140 // Force SPROG version SPROG SPROG II v3.x (also covers IIv4, SPROG 3 and Nano) 141 sv = new SprogVersion(new SprogType(SprogType.SPROGIIv3), ""); 142 } 143 blockLen = sv.sprogType.getBlockLen(); 144 // We remain in this state until program button is pushed 145 } 146 } else { 147 log.error("Bad reply to RD_VER request"); 148 bootState = BootState.IDLE; 149 tc.setSprogState(SprogState.NORMAL); 150 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusUnableToConnectBootloader"), 151 Bundle.getMessage("SprogXFirmwareUpdate"), JmriJOptionPane.ERROR_MESSAGE); 152 statusBar.setText(Bundle.getMessage("StatusUnableToConnectBootloader")); 153 } 154 } 155 156 @Override 157 synchronized protected void stateWriteSent() { 158 stopTimer(); 159 if (log.isDebugEnabled()) { 160 log.debug("reply in WRITESENT state"); 161 } 162 // Check for correct response to type of write that was sent 163 if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1) 164 || (reply.getElement(reply.getNumDataElements() - 1) == '.')) { 165 if (hexFile.read() > 0) { 166 // More data to write 167 sendWrite(); 168 } else { 169 doneWriting(); 170 } 171 } else { 172 // Houston, we have a problem 173// JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyWriteRequest"), 174// Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE); 175 log.error("Bad reply to write request"); 176 statusBar.setText(Bundle.getMessage("StatusBadReplyWriteRequest")); 177 bootState = BootState.IDLE; 178 tc.setSprogState(SprogState.NORMAL); 179 } 180 } 181 182 @Override 183 synchronized protected void stateEraseSent() { 184 stopTimer(); 185 if (log.isDebugEnabled()) { 186 log.debug("reply in ERASESENT state"); 187 } 188 // Check for correct response to erase that was sent 189 if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1)) { 190 // Don't erase ICD debug executive if in use 191 if ((sv.sprogType.sprogType < SprogType.SPROGIIv3) && (eraseAddress < 0x7c00) 192 || (sv.sprogType.sprogType >= SprogType.SPROGIIv3) && (eraseAddress < 0x3F00)) { 193 // More data to erase 194 sendErase(); 195 } else { 196 if (log.isDebugEnabled()) { 197 log.debug("Finished erasing"); 198 } 199 statusBar.setText(Bundle.getMessage("StatusEraseComplete")); 200 // Read first line from hexfile 201 if (hexFile.read() > 0) { 202 // Program line and wait for reply 203 if (log.isDebugEnabled()) { 204 log.debug("First write {} {}", hexFile.getLen(), hexFile.getAddress()); 205 } 206 sendWrite(); 207 } else { 208 doneWriting(); 209 } 210 } 211 } else { 212 // Houston, we have a problem 213// JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyErase"), 214// Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE); 215 log.error("Bad reply to erase request"); 216 bootState = BootState.IDLE; 217 tc.setSprogState(SprogState.NORMAL); 218 } 219 } 220 221 @Override 222 synchronized protected void stateSprogModeSent() { 223 stopTimer(); 224 if (log.isDebugEnabled()) { 225 log.debug("reply in SROGMODESENT state"); 226 } 227 // Check for correct response to type of write that was sent 228 if ((reply.getOpCode() == msg.getElement(2)) && (reply.getNumDataElements() == 1)) { 229 if (log.isDebugEnabled()) { 230 log.debug("Reset SPROG"); 231 } 232 msg = SprogMessage.getReset(); 233 bootState = BootState.RESETSENT; 234 tc.sendSprogMessage(msg, this); 235 startLongTimer(); 236 } else { 237 // Houston, we have a problem 238// JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("StatusBadReplyModeRequest"), 239// Bundle.getMessage("SprogXFirmwareUpdate", " II"), JmriJOptionPane.ERROR_MESSAGE); 240 log.error("Bad reply to SPROG Mode request"); 241 bootState = BootState.IDLE; 242 tc.setSprogState(SprogState.NORMAL); 243 } 244 } 245 246 @Override 247 synchronized protected void stateResetSent() { 248 stopTimer(); 249 if (log.isDebugEnabled()) { 250 log.debug("reply in RESETSENT state"); 251 } 252 // Check for correct response to type of write that was sent 253 254 statusBar.setText(Bundle.getMessage("DefaultStatusText")); // Ready, is in jmrixBundle 255 256 tc.setSprogState(SprogState.NORMAL); 257 bootState = BootState.IDLE; 258 } 259 260 @Override 261 synchronized protected void requestBoot() { 262 // Look for SPROG in boot mode by requesting bootloader version. 263 if (log.isDebugEnabled()) { 264 log.debug("Request bootloader version"); 265 } 266 // allow parsing of bootloader replies 267 if (tc == null) { 268 log.warn("requestBoot with null tc, ignored"); 269 return; 270 } 271 tc.setSprogState(SprogState.SIIBOOTMODE); 272 bootState = BootState.VERREQSENT; 273 msg = SprogMessage.getReadBootVersion(); 274 tc.sendSprogMessage(msg, this); 275 startLongTimer(); 276 } 277 278 @Override 279 synchronized protected void sendWrite() { 280 if ((hexFile.getAddressU()&0xFF) >= 0xF0) { 281 // Write to EEPROM 282 if (log.isDebugEnabled()) { 283 log.debug("Send write EE {}", hexFile.getAddress()); 284 } 285 msg = SprogMessage.getWriteEE(hexFile.getAddress(), hexFile.getData()); 286 } else if ((hexFile.getAddressU()&0xFF) >= 0x20) { 287 // Write to user data or config data not supported 288 if (log.isDebugEnabled()) { 289 log.debug("null write {}", hexFile.getAddress()); 290 } 291 msg = null; 292 } else if (sv.sprogType.isValidFlashAddress(hexFile.getAddress())) { 293 // Program code address is above bootloader range and below debug executive 294 if (log.isDebugEnabled()) { 295 log.debug("Send write Flash {}", hexFile.getAddress()); 296 } 297 msg = SprogMessage.getWriteFlash(hexFile.getAddress(), hexFile.getData(), blockLen); 298 if (log.isDebugEnabled()) { 299 log.debug("msg: {}", msg.toString(true)); 300 } 301 } else { 302 // Do nothing 303 if (log.isDebugEnabled()) { 304 log.debug("null write {}", hexFile.getAddress()); 305 } 306 msg = null; 307 } 308 if (msg != null) { 309 bootState = BootState.WRITESENT; 310 statusBar.setText(Bundle.getMessage("StatusWriteX", hexFile.getAddress())); 311 tc.sendSprogMessage(msg, this); 312 if (log.isDebugEnabled()) { 313 log.debug("Sent write command to address {}", hexFile.getAddress()); 314 } 315 startLongTimer(); 316 } else { 317 // use timeout to kick off the next write 318 bootState = BootState.NULLWRITE; 319 statusBar.setText(Bundle.getMessage("StatusSkipX", hexFile.getAddress())); 320 startVShortTimer(); 321 } 322 } 323 324 synchronized private void sendErase() { 325 if (log.isDebugEnabled()) { 326 log.debug("Erase Flash {}", eraseAddress); 327 } 328 int rows = 8; // 512 bytes 329 msg = SprogMessage.getEraseFlash(eraseAddress, rows); 330 bootState = BootState.ERASESENT; 331 statusBar.setText(Bundle.getMessage("StatusEraseX", eraseAddress)); 332 tc.sendSprogMessage(msg, this); 333 if (log.isDebugEnabled()) { 334 log.debug("Sent erase command to address {}", eraseAddress); 335 } 336 eraseAddress += (rows * 64); 337 startLongTimer(); 338 } 339 340 @Override 341 synchronized protected void doneWriting() { 342 // Finished 343 if (log.isDebugEnabled()) { 344 log.debug("Done writing"); 345 } 346 statusBar.setText(Bundle.getMessage("StatusWriteComplete")); 347 openFileChooserButton.setEnabled(false); 348 programButton.setEnabled(false); 349 350 setSprogModeButton.setEnabled(true); 351 bootState = BootState.IDLE; 352 } 353 354 @Override 355 synchronized public void programButtonActionPerformed(java.awt.event.ActionEvent e) { 356 if (hexFile != null) { 357 openFileChooserButton.setEnabled(false); 358 programButton.setEnabled(false); 359 setSprogModeButton.setEnabled(false); 360 eraseAddress = sv.sprogType.getEraseStart(); 361 if (eraseAddress > 0) { 362 if (log.isDebugEnabled()) { 363 log.debug("Start erasing @{}", eraseAddress); 364 } 365 sendErase(); 366 } 367 } 368 } 369 370 @Override 371 synchronized public void setSprogModeButtonActionPerformed(java.awt.event.ActionEvent e) { 372 if (log.isDebugEnabled()) { 373 log.debug("Set SPROG mode"); 374 } 375 msg = SprogMessage.getWriteEE(0xff, new int[]{0}); 376 bootState = BootState.SPROGMODESENT; 377 // Set TC timeout back to normal 378 tc.resetTimeout(); 379 tc.sendSprogMessage(msg, this); 380 startLongTimer(); 381 } 382 383 /** 384 * Removes SprogVersionListener. 385 * Calls Super to stop Timer. 386 * {@inheritDoc} 387 */ 388 @Override 389 public void dispose(){ 390 if (_memo !=null) { 391 _memo.getSprogVersionQuery().removeSprogVersionListener(this); 392 } 393 super.dispose(); 394 } 395 396 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogIIUpdateFrame.class); 397}