001package jmri.jmrix.can.cbus.swing.bootloader; 002 003import static javax.swing.SwingUtilities.getWindowAncestor; 004 005import java.awt.BorderLayout; 006import java.awt.Dimension; 007import java.awt.event.ActionListener; 008import java.io.IOException; 009import java.text.MessageFormat; 010import java.util.*; 011 012import javax.swing.BorderFactory; 013import javax.swing.BoxLayout; 014import javax.swing.ButtonGroup; 015import javax.swing.JButton; 016import javax.swing.JCheckBox; 017import javax.swing.JFileChooser; 018import javax.swing.JFrame; 019import javax.swing.JMenu; 020import javax.swing.JPanel; 021import javax.swing.JRadioButtonMenuItem; 022import javax.swing.JScrollPane; 023import javax.swing.JTextField; 024import javax.swing.event.DocumentEvent; 025import javax.swing.event.DocumentListener; 026import javax.swing.filechooser.FileFilter; 027import javax.swing.filechooser.FileNameExtensionFilter; 028 029import jmri.jmrix.can.CanListener; 030import jmri.jmrix.can.CanMessage; 031import jmri.jmrix.can.CanReply; 032import jmri.jmrix.can.CanSystemConnectionMemo; 033import jmri.jmrix.can.TrafficController; 034import jmri.jmrix.can.cbus.CbusMessage; 035import jmri.jmrix.can.cbus.CbusSend; 036import jmri.jmrix.can.cbus.CbusConstants; 037import jmri.jmrix.can.cbus.CbusPreferences; 038import jmri.jmrix.can.cbus.node.CbusNode; 039import jmri.util.FileUtil; 040import jmri.util.ThreadingUtil; 041import jmri.util.TimerUtil; 042import jmri.util.swing.BusyDialog; 043import jmri.util.swing.TextAreaFIFO; 044 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * Bootloader client for uploading CBUS node firmware. 050 * <p> 051 * Update March 2022 A new CBUS bootloader protocol supports two new features: 052 * - Reading back device ID 053 * - Reading back bootloader ID 054 * - Positive acknowledgement (or error) for write command 055 * - Possibility fro alternative checksum algorithms. 056 * <p> 057 * The module may buffer write commands in RAM, sending an immediate ACK and 058 * only writing when a FLASH page worth of data is received, which will result 059 * in a delayed ACK. 060 * <p> 061 * A new command, that will be ignored by the old bootloader, is used to request 062 * the bootloader ID. If no reply is received after a suitable timeout 063 * then the original protocol will be used. 064 * 065 * The old protocol is only supported for older PIC18 K8x devices. 066 * 067 * Modules based on any other devices are expected to support the new protocol. 068 * 069 * @author Andrew Crosland Copyright (C) 2020 Updates for new bootloader 070 * protocol 071 * @author Andrew Crosland Copyright (C) 2022 072 */ 073public class CbusBootloaderPane extends jmri.jmrix.can.swing.CanPanel 074 implements CanListener { 075 076 private TrafficController tc; 077 private CbusSend send; 078 private CbusPreferences preferences; 079 080 private final JRadioButtonMenuItem slowWrite; 081 private final JRadioButtonMenuItem fastWrite; 082 protected JTextField nodeNumberField = new JTextField(6); 083 protected JCheckBox configCheckBox = new JCheckBox(); 084 protected JCheckBox eepromCheckBox = new JCheckBox(); 085 protected JCheckBox moduleCheckBox = new JCheckBox(); 086 protected JButton programButton; 087 protected JButton openFileChooserButton; 088 protected JButton readNodeParamsButton; 089 private final TextAreaFIFO bootConsole; 090 private static final int MAX_LINES = 5000; 091 private final JFrame topFrame = (JFrame) getWindowAncestor(this); 092 093 // to find and remember the hex file 094 final javax.swing.JFileChooser hexFileChooser = 095 new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 096 // File to hold name of hex file 097 transient HexFile hexFile = null; 098 099 CbusParameters hardwareParams = null; 100 CbusParameters fileParams = null; 101 102 boolean hexForBootloader = false; 103 104 int nodeNumber; 105 int nextParam; 106 107 protected HexRecord currentRecord; 108 protected int recordIndex = 0; 109 protected boolean recordDone = false; 110 111 // Set Program memory upper limit for PIC18 112 // Only needed for old AN274 based bootloader, which had no acknowledge. Used 113 // to determine when to use a longer timeout for EEPROM and CONFIG. 114 // New modules should use the CBUS bootloader. 115 private static final int CONFIG_START = 0x200000; 116 117 BusyDialog busyDialog; 118 119 /** 120 * Bootloader protocol 121 */ 122 protected enum BootProtocol { 123 UNKNOWN, 124 AN247, 125 CBUS_2_0 126 } 127 protected BootProtocol bootProtocol = BootProtocol.UNKNOWN; 128 129 /** 130 * Bootloader checksum calculation 131 */ 132 protected enum BootChecksum { 133 CHECK_2S_COMPLEMENT, 134 CHECK_CRC16 135 } 136 protected BootChecksum bootChecksum = BootChecksum.CHECK_2S_COMPLEMENT; 137 138 /** 139 * Bootloader state machine states 140 */ 141 protected enum BootState { 142 IDLE, 143 GET_PARAMS, 144 START_BOOT, 145 CHECK_BOOT_MODE, 146 WAIT_BOOT_DEVID, 147 WAIT_BOOT_ID, 148 ENABLES_SENT, 149 INIT_SENT, 150 PROG_DATA, 151 PROG_PAUSE, 152 CHECK_SENT, 153 NOP_SENT 154 } 155 protected BootState bootState = BootState.IDLE; 156 157 /** 158 * Bootloader status values 159 */ 160 protected enum BootStatus { 161 NONE, 162 PARAMETER_TIMEOUT, 163 INIT_OUT_OF_RANGE, 164 DATA_ERROR, 165 DATA_OUT_OF_RANGE, 166 ADDRESS_OUT_OF_RANGE, 167 CHECKSUM_FAILED, 168 ADDRESS_NOT_FOUND, 169 COMPLETE, 170 BOOT_TIMEOUT, 171 ACK_TIMEOUT, 172 CHECKSUM_TIMEOUT, 173 PROTOCOL_ERROR 174 } 175 176 protected int bootAddress; 177 protected int checksum; 178 protected int dataFramesSent; 179 protected int dataTimeout; 180 181 182 public CbusBootloaderPane() { 183 super(); 184 bootConsole = new TextAreaFIFO(MAX_LINES); 185 slowWrite = new JRadioButtonMenuItem(Bundle.getMessage("Slow")); 186 fastWrite = new JRadioButtonMenuItem(Bundle.getMessage("Fast")); 187 } 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override 193 public void initComponents(CanSystemConnectionMemo memo) { 194 super.initComponents(memo); 195 196 // connect to the CanInterface 197 tc = memo.getTrafficController(); 198 addTc(tc); 199 200 send = new CbusSend(memo, bootConsole); 201 202 preferences = memo.get(jmri.jmrix.can.cbus.CbusPreferences.class); 203 204 init(); 205 setMenuOptions(); 206 } 207 208 209 /** 210 * Not sure this comment really applies here as init() does not use the tc 211 * Don't use initComponent() as memo doesn't yet exist when that gets called. 212 * Instead, call init() function from initComponents(memo) 213 */ 214 public void init() { 215 bootConsole.setEditable(false); 216 217 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 218 219 // Node number selector 220 JPanel nnPane = new JPanel(); 221 nnPane.setBorder(BorderFactory.createTitledBorder( 222 BorderFactory.createEtchedBorder(), Bundle.getMessage("BootNodeNumber"))); 223 nnPane.add(nodeNumberField); 224 225 nodeNumberField.setText(""); 226 nodeNumberField.setToolTipText(Bundle.getMessage("BootNodeNumberTT")); 227 nodeNumberField.setMaximumSize(nodeNumberField.getPreferredSize()); 228 // Reset the buttons and clear parameters when a new node is selected 229 nodeNumberField.getDocument().addDocumentListener( 230 new DocumentListener() { 231 @Override 232 public void changedUpdate(DocumentEvent e) { 233 resetButtons(); 234 } 235 236 @Override 237 public void removeUpdate(DocumentEvent e) { 238 resetButtons(); 239 } 240 241 @Override 242 public void insertUpdate(DocumentEvent e) { 243 resetButtons(); 244 } 245 246 public void resetButtons() { 247 openFileChooserButton.setEnabled(false); 248 programButton.setEnabled(false); 249 hardwareParams = null; 250 } 251 } 252 ); 253 nnPane.add(nodeNumberField); 254 255 // Memory options 256 configCheckBox.setText(Bundle.getMessage("BootWriteConfigWords")); 257 configCheckBox.setVisible(true); 258 configCheckBox.setEnabled(true); 259 configCheckBox.setSelected(false); 260 configCheckBox.setToolTipText(Bundle.getMessage("BootWriteConfigWordsTT")); 261 262 eepromCheckBox.setText(Bundle.getMessage("BootWriteEeprom")); 263 eepromCheckBox.setVisible(true); 264 eepromCheckBox.setEnabled(true); 265 eepromCheckBox.setSelected(false); 266 eepromCheckBox.setToolTipText(Bundle.getMessage("BootWriteEepromTT")); 267 268 JPanel memoryPane = new JPanel(); 269 memoryPane.setBorder(BorderFactory.createTitledBorder( 270 BorderFactory.createEtchedBorder(), Bundle.getMessage("BootMemoryOptions"))); 271 memoryPane.setLayout(new BoxLayout(memoryPane, BoxLayout.X_AXIS)); 272 memoryPane.add(configCheckBox); 273 memoryPane.add(eepromCheckBox); 274 275 // Module sanity check 276 moduleCheckBox.setText(Bundle.getMessage("BootIgnoreParams")); 277 moduleCheckBox.setVisible(true); 278 moduleCheckBox.setEnabled(true); 279 moduleCheckBox.setSelected(false); 280 moduleCheckBox.setToolTipText(Bundle.getMessage("BootIgnoreParamsTT")); 281 282 JPanel modulePane = new JPanel(); 283 modulePane.setBorder(BorderFactory.createTitledBorder( 284 BorderFactory.createEtchedBorder(), Bundle.getMessage("BootModuleOptions"))); 285 modulePane.setLayout(new BoxLayout(modulePane, BoxLayout.X_AXIS)); 286 modulePane.add(moduleCheckBox); 287 288 JPanel selectPane = new JPanel(); 289 selectPane.setLayout(new BoxLayout(selectPane, BoxLayout.X_AXIS)); 290 selectPane.add(nnPane); 291 selectPane.add(modulePane); 292 selectPane.add(memoryPane); 293 294 // Create buttons 295 readNodeParamsButton = new JButton(Bundle.getMessage("BootReadNodeParams")); 296 readNodeParamsButton.setVisible(true); 297 readNodeParamsButton.setEnabled(true); 298 readNodeParamsButton.setToolTipText(Bundle.getMessage("BootReadNodeParamsTT")); 299 readNodeParamsButton.addActionListener((java.awt.event.ActionEvent e) -> { 300 readNodeParamsButtonActionPerformed(e); 301 }); 302 303 FileFilter filter = new FileNameExtensionFilter("Hex file", new String[] {"hex"}); 304 hexFileChooser.setFileFilter(filter); 305 hexFileChooser.addChoosableFileFilter(filter); 306 307 openFileChooserButton = new JButton(Bundle.getMessage("BootChooseFile")); 308 openFileChooserButton.setVisible(true); 309 openFileChooserButton.setEnabled(false); 310 openFileChooserButton.setToolTipText(Bundle.getMessage("BootChooseFileTT")); 311 openFileChooserButton.addActionListener((java.awt.event.ActionEvent e) -> { 312 openFileChooserButtonActionPerformed(e); 313 }); 314 315 programButton = new JButton(Bundle.getMessage("BootStartProgramming")); 316 programButton.setVisible(true); 317 programButton.setEnabled(false); 318 programButton.setToolTipText(Bundle.getMessage("BootStartProgrammingTT")); 319 programButton.addActionListener((java.awt.event.ActionEvent e) -> { 320 programButtonActionPerformed(e); 321 }); 322 323 // add pane to hold buttons 324 JPanel buttonPane = new JPanel(); 325 buttonPane.setBorder(BorderFactory.createTitledBorder( 326 BorderFactory.createEtchedBorder(), "")); 327 buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); 328 buttonPane.add(readNodeParamsButton); 329 buttonPane.add(openFileChooserButton); 330 buttonPane.add(programButton); 331 332 JPanel topPane = new JPanel(); 333 topPane.setLayout(new BoxLayout(topPane, BoxLayout.Y_AXIS)); 334 topPane.add(selectPane); 335 topPane.add(buttonPane); 336 337 // Scroll pane for feedback area 338 JScrollPane feedbackScroll = new JScrollPane(bootConsole); 339 feedbackScroll.setBorder(BorderFactory.createTitledBorder( 340 BorderFactory.createEtchedBorder(), Bundle.getMessage("BootConsole"))); 341 feedbackScroll.setPreferredSize(new Dimension(400, 200)); 342 343 // Now add to a border layout so that scroll pane will absorb space 344 JPanel pane1 = new JPanel(); 345 pane1.setLayout(new BorderLayout()); 346 pane1.add(topPane, BorderLayout.PAGE_START); 347 pane1.add(feedbackScroll, BorderLayout.CENTER); 348 349 add(pane1); 350 351 setVisible(true); 352 } 353 354 355 /** 356 * {@inheritDoc} 357 */ 358 @Override 359 public String getTitle() { 360 return prependConnToString(Bundle.getMessage("MenuItemBootloader")); 361 } 362 363 364 /** 365 * Set Menu Options, e.g., which checkboxes, etc., should be checked 366 */ 367 private void setMenuOptions(){ 368 slowWrite.setSelected(false); 369 fastWrite.setSelected(false); 370 371 switch (preferences.getBootWriteDelay()) { 372 case 10: 373 fastWrite.setSelected(true); 374 break; 375 case 50: 376 slowWrite.setSelected(true); 377 break; 378 default: 379 break; 380 } 381 } 382 383 384 /** 385 * Creates a Menu List. 386 * 387 * {@inheritDoc} 388 */ 389 @Override 390 public List<JMenu> getMenus() { 391 List<JMenu> menuList = new ArrayList<>(); 392 393 JMenu optionsMenu = new JMenu(Bundle.getMessage("Options")); 394 395 JMenu writeSpeedMenu = new JMenu(Bundle.getMessage("BootWriteSpeed")); 396 ButtonGroup backgroundFetchGroup = new ButtonGroup(); 397 398 backgroundFetchGroup.add(slowWrite); 399 backgroundFetchGroup.add(fastWrite); 400 401 writeSpeedMenu.add(slowWrite); 402 writeSpeedMenu.add(fastWrite); 403 404 optionsMenu.add(writeSpeedMenu); 405 406 menuList.add(optionsMenu); 407 408 // saved preferences go through the cbus table model so they can be actioned immediately 409 // they'll be also saved by the table, not here. 410 411 // values need to match setMenuOptions() 412 ActionListener writeSpeedListener = ae -> { 413 if (slowWrite.isSelected()) { 414 preferences.setBootWriteDelay(CbusNode.BOOT_PROG_TIMEOUT_SLOW); 415 } 416 else if (fastWrite.isSelected()) { 417 preferences.setBootWriteDelay(CbusNode.BOOT_PROG_TIMEOUT_FAST); 418 } 419 }; 420 slowWrite.addActionListener(writeSpeedListener); 421 fastWrite.addActionListener(writeSpeedListener); 422 423 return menuList; 424 } 425 426 427 /** 428 * Get the delay to be inserted between bootloader data writes. 429 * 430 * For AN247, that has no handshaking can be slow or fast and then extended 431 * for slow writes to EEPROM and CONFIG. 432 * 433 * Only a single long timeout is used for CBUS protocol, which has full 434 * handshaking 435 * 436 * @return Delay in ms 437 */ 438 int getWriteDelay() { 439 int delay = CbusNode.BOOT_PROG_TIMEOUT_FAST; 440 441 if (bootProtocol == BootProtocol.AN247) { 442 if (slowWrite.isSelected()) { 443 delay = CbusNode.BOOT_PROG_TIMEOUT_SLOW; 444 } 445 if (bootAddress >= CONFIG_START) { 446 delay *= 8; 447 } 448 } else { 449 delay = CbusNode.BOOT_LONG_TIMEOUT_TIME; 450 } 451 452 return delay; 453 } 454 455 456 /** 457 * Kick off the reading of parameters from the node, starting with parameter 458 * 0, the number of parameters 459 * 460 * @param e 461 */ 462 private void readNodeParamsButtonActionPerformed(java.awt.event.ActionEvent e) { 463 try { 464 nodeNumber = Integer.parseInt(nodeNumberField.getText()); 465 } catch (NumberFormatException e1) { 466 addToLog(Bundle.getMessage("BootInvalidNode")); 467 log.error("Invalid node number {}", nodeNumberField.getText()); 468 return; 469 } 470 // Read the parameters from the chosen node 471 addToLog(Bundle.getMessage("BootReadingParams")); 472 hardwareParams = new CbusParameters(); 473 nextParam = 0; 474 busyDialog = new BusyDialog(topFrame, Bundle.getMessage("BootReadingParams"), false); 475 busyDialog.start(); 476 requestParam(nextParam); 477 } 478 479 480 /** 481 * Let the user choose the hex file and check that it is suitable for the 482 * selected node. 483 * 484 * @param e 485 */ 486 private void openFileChooserButtonActionPerformed(java.awt.event.ActionEvent e) { 487 // start at current file, show dialog 488 int retVal = hexFileChooser.showOpenDialog(this); 489 // handle selection or cancel 490 if (retVal == JFileChooser.APPROVE_OPTION) { 491 // Nothing can be programmed until the new file has been read and checked 492 programButton.setEnabled(false); 493 hexForBootloader = false; 494 fileParams = new CbusParameters(); 495 // Release any previously loaded file 496 if (hexFile != null) { 497 hexFile.dispose(); 498 hexFile = null; 499 } 500 501 CbusPicHexFile newHexFile = new CbusPicHexFile(hexFileChooser.getSelectedFile().getPath()); 502 log.debug("hex file chosen: {}", newHexFile.getName()); 503 addToLog(MessageFormat.format(Bundle.getMessage("BootFileChosen"), newHexFile.getName())); 504 try { 505 newHexFile.openRd(); 506 newHexFile.read(); 507 } catch (IOException ex) { 508 log.error("Error opening hex file: {}", ex.getMessage()); 509 addToLog(Bundle.getMessage("BootHexFileOpenFailed")); 510 return; 511 } finally { 512 // The whole file is held in memory so the file itself can be closed now 513 newHexFile.close(); 514 } 515 516 // Only use the file once it has been read successfully 517 hexFile = newHexFile; 518 fileParams = hexFile.getParams(); 519 boolean hardwareParamsValid = (hardwareParams != null) && hardwareParams.areValid(); 520 if (!moduleCheckBox.isSelected()) { 521 if (fileParams.validate(fileParams, hardwareParams)) { 522 addToLog(MessageFormat.format(Bundle.getMessage("BootHexFileFoundParameters"), fileParams.toString())); 523 addToLog(Bundle.getMessage("BootHexFileParametersMatch")); 524 programButton.setEnabled(true); 525 } else { 526 addToLog(Bundle.getMessage("BootHexFileParametersMismatch")); 527 } 528 } else { 529 addToLog(Bundle.getMessage("BootHexFileIgnoringParameters")); 530 programButton.setEnabled(true); 531 } 532 if (hardwareParamsValid && (hardwareParams.getLoadAddress() == 0)) { 533 // Special case of rewriting the bootloader for Pi-SPROG One 534 addToLog(Bundle.getMessage("BootBoot")); 535 hexForBootloader = true; 536 programButton.setEnabled(true); 537 } 538 } 539 } 540 541 542 /** 543 * Send BOOTM OPC to put module in boot mode 544 * 545 * @param e 546 */ 547 private void programButtonActionPerformed(java.awt.event.ActionEvent e) { 548 if (hasActiveTimers()){ 549 return; 550 } 551 if (hexFile == null) { 552 log.error("No hex file loaded"); 553 addToLog(Bundle.getMessage("BootHexFileOpenFailed")); 554 programButton.setEnabled(false); 555 return; 556 } 557 openFileChooserButton.setEnabled(false); 558 programButton.setEnabled(false); 559 busyDialog = new BusyDialog(topFrame, Bundle.getMessage("BootLoading"), false); 560 busyDialog.start(); 561 setStartBootTimeout(); 562 bootState = BootState.START_BOOT; 563 CanMessage m = CbusMessage.getBootEntry(nodeNumber, 0); 564 tc.sendCanMessage(m, null); 565 } 566 567 568 /** 569 * Process some outgoing CAN frames 570 * <p> 571 * The CBUS bootloader was originally "fire and forget", with no positive 572 * acknowledgement. We had to wait an indeterminate time and assume the 573 * write was successful. 574 * <p> 575 * A PIC based node will halt execution for some time ((10+ ms with newer Q 576 * series devices) whilst FLASH operations (erase and/or write) complete, 577 * during which time I/O will not be serviced. This is probably OK with CAN 578 * transport, assuming the ECAN continues to accept frames. With serial 579 * (UART) transport, as used by Pi-SPROG, the timing is much more critical 580 * as a single missed character will corrupt the node firmware. 581 * <p> 582 * Furthermore, on some platforms, e.g., Raspberry Pi, there can be 583 * considerable delays between the call to the traffic controller 584 * sendMessage() method and the message being sent by the transmit thread. 585 * This may be due to Flash file system operations and could be affected by 586 * the speed of the SD card. Once the message leaves the transmit thread, we 587 * are at the mercy of the underlying OS, where there can be further delays. 588 * <p> 589 * We could set an overlong timeout, but that would slow down the bootloading 590 * process in all cases. 591 * <p> 592 * To improve things somewhat we wait until the message has definitely 593 * reached the TC transmit thread, by looking for bootloader data write 594 * messages here. Testing indicates this is a marked improvement with no 595 * failures observed. 596 * 597 * This is unnecessary, and not used, for the new protocol which has a 598 * positive acknowledge mechanism. 599 * 600 * @param m CanMessage 601 */ 602 @Override 603 public void message(CanMessage m) { 604 if (bootProtocol == BootProtocol.AN247) { 605 if ((bootState == BootState.PROG_DATA)) { 606 if (m.isExtended() ) { 607 if (CbusMessage.isBootWriteData(m)) { 608 log.debug("Boot data write message {}", m); 609 setDataTimeout(dataTimeout); 610 } 611 } 612 } 613 } 614 } 615 616 617 /** 618 * Processes incoming CAN replies 619 * <p> 620 * The bootloader is only interested in standard parameter responses and 621 * extended bootloader responses. 622 * 623 * {@inheritDoc} 624 */ 625 @Override 626 public void reply(CanReply r) { 627 628 if ( r.isRtr() ) { 629 return; 630 } 631 632 if (!r.isExtended() ) { 633 log.debug("Standard Reply {}", r); 634 635 handleStandardReply(r); 636 } else { 637// log.debug("Extended Reply {} in state {}", r, bootState); 638 // Extended messages are only used by the bootloader 639 640 handleExtendedReply(r); 641 } 642 } 643 644 645 /** 646 * Handle standard ID CAN replies 647 * 648 * @param r Can reply 649 */ 650 private void handleStandardReply(CanReply r) { 651 int opc = CbusMessage.getOpcode(r); 652 if (bootState != BootState.GET_PARAMS) { 653 log.debug("Reply not for me"); 654 return; 655 } 656 657 if ( opc == CbusConstants.CBUS_PARAN) { // response from node 658 if (hardwareParams == null) { 659 log.debug("Ignoring PARAN reply, no parameters being read"); 660 return; 661 } 662 // Only interested in replies from the node being programmed 663 int nn = (r.getElement(1) * 256) + r.getElement(2); 664 if (nn != nodeNumber) { 665 log.debug("Ignoring PARAN reply from node {}, expecting node {}", nn, nodeNumber); 666 return; 667 } 668 int index = r.getElement(3); 669 if (!hardwareParams.isValidIndex(index)) { 670 log.warn("Ignoring PARAN reply from node {} with out of range parameter index {}", nn, index); 671 return; 672 } 673 if (index != nextParam) { 674 log.debug("Ignoring PARAN reply for parameter {}, expecting parameter {}", index, nextParam); 675 return; 676 } 677 clearAllParamTimeout(); 678 679 hardwareParams.setParam(index, r.getElement(4)); 680 if ((++nextParam < (hardwareParams.getParam(0) + 1)) && hardwareParams.isValidIndex(nextParam)) { 681 // Read next 682 requestParam(nextParam); 683 } else { 684 // Done reading 685 hardwareParams.setValid(true); 686 addToLog(MessageFormat.format(Bundle.getMessage("BootNodeParametersFinished"), hardwareParams.toString())); 687 busyDialog.finish(); 688 busyDialog = null; 689 openFileChooserButton.setEnabled(true); 690 bootState = BootState.IDLE; 691 } 692 } else { 693 // ignoring OPC 694 } 695 } 696 697 698 /** 699 * Handle extended ID CAN replies 700 * <p> 701 * Handle the reply in the bootloader state machine. 702 * 703 * @param r Can reply 704 */ 705 private void handleExtendedReply(CanReply r) { 706 switch (bootState) { 707 default: 708 break; 709 710 case CHECK_BOOT_MODE: 711 clearCheckBootTimeout(); 712 if (CbusMessage.isBootConfirm(r)) { 713 // The node is in boot mode so we can look for the device ID 714 requestDevId(); 715 } 716 break; 717 718 case WAIT_BOOT_DEVID: 719 clearDevIdTimeout(); 720 if (CbusMessage.isBootDevId(r)) { 721 // We had a response to the Device ID request so we can proceed with the new protocol 722 showDevId(r); 723 bootProtocol = BootProtocol.CBUS_2_0; 724 requestBootId(); 725 } else { 726 protocolError(); 727 } 728 break; 729 730 case WAIT_BOOT_ID: 731 clearBootIdTimeout(); 732 if (CbusMessage.isBootId(r)) { 733 // We had a response to the bootloader ID request so send the write enables 734 showBootId(r); 735 sendBootEnables(); 736 } else { 737 protocolError(); 738 } 739 break; 740 741 case ENABLES_SENT: 742 clearAckTimeout(); 743 if (CbusMessage.isBootOK(r)) { 744 // We had a response to the enables so start programming. 745 initialise(); 746 } else { 747 protocolError(); 748 } 749 break; 750 751 case INIT_SENT: 752 clearAckTimeout(); 753 if (CbusMessage.isBootOK(r)) { 754 // We had a response to the initislise so start programming. 755 writeNextData(); 756 } else if (CbusMessage.isBootOutOfRange(r)) { 757 log.error("INIT Address out of range"); 758 endProgramming(BootStatus.INIT_OUT_OF_RANGE); 759 } else { 760 protocolError(); 761 } 762 break; 763 764 case PROG_DATA: 765 clearAckTimeout(); 766 if (CbusMessage.isBootDataOK(r)) { 767 // Acknowledge received for CBUS protocol 768 writeNextData(); 769 } else if (CbusMessage.isBootError(r)){ 770 log.error("Data Error"); 771 endProgramming(BootStatus.DATA_ERROR); 772 } else if (CbusMessage.isBootDataOutOfRange(r)) { 773 log.error("Data Address out of range"); 774 endProgramming(BootStatus.DATA_OUT_OF_RANGE); 775 } else { 776 protocolError(); 777 } 778 break; 779 780 case NOP_SENT: 781 clearAckTimeout(); 782 if (CbusMessage.isBootOK(r)) { 783 // Acknowledge received for NOP 784 bootState = BootState.PROG_DATA; 785 writeNextData(); 786 } else if (CbusMessage.isBootOutOfRange(r)) { 787 log.error("NOP Address out of range"); 788 endProgramming(BootStatus.ADDRESS_OUT_OF_RANGE); 789 } else { 790 protocolError(); 791 } 792 break; 793 794 case CHECK_SENT: 795 clearCheckTimeout(); 796 if (CbusMessage.isBootOK(r)) { 797 sendReset(); 798 } else if (CbusMessage.isBootError(r)) { 799 // Checksum verify failed 800 log.error("Node {} checksum failed", nodeNumber); 801 endProgramming(BootStatus.CHECKSUM_FAILED); 802 } else { 803 protocolError(); 804 } 805 break; 806 } 807 } 808 809 810 /** 811 * Show the device ID 812 * 813 * Manufacturere and device from cbusdefs.h, device ID from the device 814 * 815 * @param r device ID reply 816 */ 817 void showDevId(CanReply r) { 818 long devId = getDevId(r); 819 log.debug("Found device ID Manu: {} Dev: {} Device ID: {}", 820 r.getElement(1), 821 r.getElement(2), 822 devId); 823 addToLog(MessageFormat.format(Bundle.getMessage("DevIdCbus"), 824 r.getElement(1), 825 r.getElement(2), 826 devId)); 827 } 828 829 830 /** 831 * Extract the 4-byte device ID from a device ID reply 832 * <p> 833 * Reply data is [CBUS_EXT_DEVID, manufacturer, device, ID3, ID2, ID1, ID0] 834 * 835 * @param r device ID reply 836 * @return the device ID, as an unsigned value 837 */ 838 static long getDevId(CanReply r) { 839 return ((long)(r.getElement(3) & 0xFF) << 24) 840 + ((r.getElement(4) & 0xFF) << 16) 841 + ((r.getElement(5) & 0xFF) << 8) 842 + (r.getElement(6) & 0xFF); 843 } 844 845 846 /** 847 * Show the bootloader ID 848 * 849 * Major/Minor version number, checksum algorithm error report capability 850 * 851 * @param r Bootloader ID reply 852 */ 853 void showBootId(CanReply r) { 854 log.debug("Found bootloader Major: {} Minor: {} Algo: {} Reports: {}", 855 r.getElement(1), 856 r.getElement(2), 857 r.getElement(3), 858 r.getElement(4)); 859 addToLog(MessageFormat.format(Bundle.getMessage("BootIdCbus"), 860 r.getElement(1), 861 r.getElement(2), 862 r.getElement(3), 863 r.getElement(4))); 864 } 865 866 867 /** 868 * Send the memory region write enable bit mask for CBUS bootloader protocol 869 */ 870 void sendBootEnables() { 871 int enables = 1; // Prog mem always enabled 872 873 if (eepromCheckBox.isSelected()) { 874 enables |= 2; 875 } 876 if (configCheckBox.isSelected()) { 877 enables |= 4; 878 } 879 880 bootState = BootState.ENABLES_SENT; 881 setAckTimeout(); 882 CanMessage m = CbusMessage.getBootEnables(enables, 0); 883 log.debug("Send boot enables {}", enables); 884 addToLog(MessageFormat.format(Bundle.getMessage("BootEnables"), enables)); 885 tc.sendCanMessage(m, null); 886 } 887 888 889 /** 890 * Protocol Error 891 */ 892 void protocolError() { 893 log.error("Bootloader Protocol Error in state {}", bootState.toString()); 894 addToLog(MessageFormat.format(Bundle.getMessage("BootProtocol"), bootState.toString())); 895 endProgramming(BootStatus.PROTOCOL_ERROR); 896 } 897 898 899 /** 900 * Is Programming Needed 901 * 902 * Check if any data bytes actually need programming 903 * 904 * @param d data bytes to check 905 * @return false if all bytes are 0xFF, else true 906 */ 907 boolean isProgrammingNeeded(byte [] d) { 908 for (int i = 0; i < d.length; i++) { 909 if (d[i] != (byte)0xFF) { 910 return true; 911 } 912 } 913 return false; 914 } 915 916 917 protected void logFrame(CanMessage m) { 918 log.debug("Write frame {} at address {} {}", dataFramesSent, Integer.toHexString(bootAddress), m); 919 if ((bootAddress & 0xFF) == 0) { 920 addToLog(MessageFormat.format(Bundle.getMessage("BootAddress"), Integer.toHexString(bootAddress))); 921 } else { 922 bootConsole.append("."); 923 } 924 } 925 926 927 /** 928 * Check if data is filtered (e.g., EEPROM selection unticked) 929 * 930 * Used only for AN247 931 * 932 * @param address of data record 933 * @return true if data is filtered and should not be written 934 */ 935 protected boolean dataIsFiltered(int address) { 936 if ((address >= 0x300000) && (address < 0x310000) && (!configCheckBox.isSelected())) { 937 // PIC18 Config space at 0x200000 is filtered 938 return true; 939 } else if ((address >= 0x310000) && (!eepromCheckBox.isSelected())) { 940 // PIC18 EEPROM space at 0x300000, 0x310000 or 0x380000 is filtered 941 return true; 942 } 943 return false; 944 } 945 946 947 /** 948 * Send data to the hardware and keep a running checksum 949 * 950 * @param timeout timeout for write operation 951 */ 952 protected void sendData(int timeout) { 953 954 byte [] d = getDataFromRecord(); 955 dataFramesSent++; 956 957 CanMessage m = CbusMessage.getBootWriteData(d, 0); 958 if (bootProtocol == BootProtocol.CBUS_2_0) { 959 setAckTimeout(); 960 updateChecksum(d); 961 logFrame(m); 962 tc.sendCanMessage(m, null); 963 } else { 964 // For AN247 protocol, we need to filter data 965 if (!dataIsFiltered(bootAddress)) { 966 // Timeout will be set when we see the outgoing message 967 dataTimeout = timeout; 968 updateChecksum(d); 969 logFrame(m); 970 tc.sendCanMessage(m, null); 971 } else { 972 // No data to send, set short timeout to trigger next data 973 setDataTimeout(10); 974 } 975 } 976 bootAddress += d.length; 977 } 978 979 980 /** 981 * Extract data from the current hex record 982 * 983 * Returns 8 byte array or whatever is left in the record if less than 8 bytes. 984 * 985 * Sets recordDone flag if record is exhausted. 986 * 987 * @return data array 988 */ 989 private byte [] getDataFromRecord() { 990 byte [] d; 991 992 if (currentRecord.len - recordIndex >= 8) { 993 d = new byte[8]; 994 if (currentRecord.len - recordIndex == 8) { 995 recordDone = true; 996 } 997 } else { 998 d = new byte[currentRecord.len - recordIndex]; 999 recordDone = true; 1000 } 1001 for (int i = 0; i < d.length; i++) { 1002 d[i] = currentRecord.getData(recordIndex++); 1003 } 1004 return d; 1005 } 1006 1007 1008 /** 1009 * Write next data for AN247 protocol 1010 */ 1011 void writeNextDataAn247() { 1012// log.debug("writeNextDataAn247()"); 1013 1014 if ((bootAddress == 0x7f8) && (hexForBootloader == true)) { 1015 log.debug("Pause for bootloader reset"); 1016 // Special case for Pi-SPROG One, pause at end of bootloader code to allow time for node to reset 1017 bootAddress = 0x800; 1018 checksum = 0; 1019 bootState = BootState.PROG_PAUSE; 1020 setPauseTimeout(); 1021 } else { 1022 // If the address has skipped we need to send a new address to the bootloader 1023 // There's no ACK so send data immediately afterwards 1024 if ((currentRecord.address + recordIndex) != bootAddress) { 1025 bootAddress = currentRecord.address; 1026 // Send NOP to adjust the address, no reply to this from AN247 1027 log.debug("Start writing at new address {}", Integer.toHexString(bootAddress)); 1028 addToLog(MessageFormat.format(Bundle.getMessage("BootNewAddress"), Integer.toHexString(bootAddress))); 1029 CanMessage m = CbusMessage.getBootNop(bootAddress, 0); 1030 tc.sendCanMessage(m, null); 1031 } 1032 if ((bootAddress < CONFIG_START) && (currentRecord.len%8 != 0) 1033 && ((currentRecord.len + (8 - currentRecord.len%8)) <= currentRecord.data.length)) { 1034 // AN247 bootloader always writes 8 bytes to FLASH so we need to pad the packet and adjust the length 1035 int pad = 8 - currentRecord.len%8; 1036 for (int i = 0; i < pad; i++) { 1037 currentRecord.data[currentRecord.len + i] = (byte)0xFF; 1038 } 1039 currentRecord.len += pad; 1040 } 1041 sendData(getWriteDelay()); 1042 } 1043 } 1044 1045 1046 /** 1047 * Write next data for CBUS protocol 1048 */ 1049 void writeNextDataCbus() { 1050// log.debug("writeNextDataCbus()"); 1051 1052 // If the address has skipped we need to send a new address to the bootloader 1053 if ((currentRecord.address + recordIndex) != bootAddress) { 1054 bootAddress = currentRecord.address; 1055 // Send NOP to adjust the address 1056 log.debug("Start writing at new address {}", Integer.toHexString(bootAddress)); 1057 addToLog(MessageFormat.format(Bundle.getMessage("BootNewAddress"), Integer.toHexString(bootAddress))); 1058 bootState = BootState.NOP_SENT; 1059 setAckTimeout(); 1060 CanMessage m = CbusMessage.getBootNop(bootAddress, 0); 1061 tc.sendCanMessage(m, null); 1062 } else { 1063 // Extract the data, send it and update bootAddress for next packet 1064 sendData(getWriteDelay()); 1065 } 1066 } 1067 1068 1069 /** 1070 * Write the next data frame for the bootloader 1071 */ 1072 void writeNextData() { 1073 if (recordDone) { 1074 // Current record is exhausted, Get next ONE 1075 recordDone = false; 1076 recordIndex = 0; 1077 if (hexFile == null) { 1078 log.error("No hex file loaded"); 1079 endProgramming(BootStatus.ADDRESS_NOT_FOUND); 1080 return; 1081 } 1082 currentRecord = hexFile.getNextRecord(); 1083 if (currentRecord.type == HexRecord.END) { 1084 // No more data to send so send checksum 1085 bootState = BootState.CHECK_SENT; 1086 addToLog(Bundle.getMessage("BootVerifyChecksum")); 1087 log.debug("Sending checksum {} as 2s complement {}", checksum, 0 - checksum); 1088 setCheckTimeout(); 1089 CanMessage m = CbusMessage.getBootCheck(0 - checksum, 0); 1090 tc.sendCanMessage(m, null); 1091 return; 1092 } 1093 } 1094 1095 bootState = BootState.PROG_DATA; 1096 if (bootProtocol == BootProtocol.AN247) { 1097 writeNextDataAn247(); 1098 } else { 1099 writeNextDataCbus(); 1100 } 1101 } 1102 1103 1104 /** 1105 * Initialise programming 1106 * 1107 * We normally start at the address from the module parameters, or from the 1108 * hex file, otherwise start at the beginning of the hex file. 1109 */ 1110 private void initialise() { 1111 Optional<HexRecord> hexRecord; 1112 1113 if (hexFile == null) { 1114 log.error("No hex file loaded"); 1115 endProgramming(BootStatus.ADDRESS_NOT_FOUND); 1116 return; 1117 } 1118 1119 if ((hardwareParams != null) && hardwareParams.areValid()) { 1120 bootAddress = hardwareParams.getLoadAddress(); 1121 } else if ((fileParams != null) && fileParams.areValid()) { 1122 bootAddress = fileParams.getLoadAddress(); 1123 } else { 1124 bootAddress = hexFile.getProgStart(); 1125 } 1126 1127 recordDone = false; 1128 recordIndex = 0; 1129 1130 hexRecord = hexFile.getRecordForAddress(bootAddress); 1131 if (hexRecord.isPresent()) { 1132 currentRecord = hexRecord.get(); 1133 } else { 1134 log.error("Did not find hex record for load address {}", "0x"+Integer.toHexString(bootAddress)); 1135 endProgramming(BootStatus.ADDRESS_NOT_FOUND); 1136 return; 1137 } 1138 checksum = 0; 1139 dataFramesSent = 0; 1140 log.debug("Initialise at address {}", "0x"+Integer.toHexString(bootAddress)); 1141 addToLog(MessageFormat.format(Bundle.getMessage("BootStartAddress"), Integer.toHexString(bootAddress))); 1142 // Initialise the bootloader, only CBUS protocol will ACK this 1143 if (bootProtocol == BootProtocol.CBUS_2_0) { 1144 setAckTimeout(); 1145 } 1146 CanMessage m = CbusMessage.getBootInitialise(bootAddress, 0); 1147 bootState = BootState.INIT_SENT; 1148 tc.sendCanMessage(m, null); 1149 if (bootProtocol == BootProtocol.AN247) { 1150 // No wait for ACK so start sending data 1151 writeNextData(); 1152 } 1153 } 1154 1155 1156 protected void requestDevId() { 1157 CanMessage m = CbusMessage.getBootDevId(0); 1158 log.debug("Requesting bootloader device ID..."); 1159 addToLog(Bundle.getMessage("ReqDevId")); 1160 bootState = BootState.WAIT_BOOT_DEVID; 1161 setDevIdTimeout(); 1162 tc.sendCanMessage(m, null); 1163 } 1164 1165 1166 protected void requestBootId() { 1167 CanMessage m = CbusMessage.getBootId(0); 1168 log.debug("Requesting bootloader ID..."); 1169 addToLog(Bundle.getMessage("ReqBootId")); 1170 bootState = BootState.WAIT_BOOT_ID; 1171 setBootIdTimeout(); 1172 tc.sendCanMessage(m, null); 1173 } 1174 1175 1176 /** 1177 * Send bootloader reset frame to put the node back into operating mode. 1178 * 1179 * There will be no reply to this. 1180 */ 1181 protected void sendReset() { 1182 CanMessage m = CbusMessage.getBootReset(0); 1183 log.debug("Done. Resetting node..."); 1184 addToLog(Bundle.getMessage("BootFinished")); 1185 tc.sendCanMessage(m, null); 1186 endProgramming(BootStatus.COMPLETE); 1187 } 1188 1189 1190 /** 1191 * Tidy up after programming success or failure 1192 */ 1193 private void endProgramming(BootStatus status) { 1194 log.debug("Boot status is {}", status.toString()); 1195 addToLog(MessageFormat.format(Bundle.getMessage("BootStatus"), status.toString())); 1196 if (busyDialog != null) { 1197 busyDialog.finish(); 1198 busyDialog = null; 1199 } 1200 openFileChooserButton.setEnabled(true); 1201 programButton.setEnabled(false); 1202 bootState = BootState.IDLE; 1203 } 1204 1205 1206 /** 1207 * Add array of bytes to checksum 1208 * 1209 * @param d the array of bytes 1210 */ 1211 protected void updateChecksum(byte [] d) { 1212 for (int i = 0; i < d.length; i++) { 1213 // bytes are signed so Cast to int and take the 8 LSBs 1214 checksum += d[i] & 0xFF; 1215 } 1216 } 1217 1218 1219 /** 1220 * Request a single Parameter from a Physical Node 1221 * <p> 1222 * Will not send the request if there are existing active timers. 1223 * Starts Parameter timeout 1224 * 1225 * @param param Parameter Index Number, Index 0 is total parameters 1226 */ 1227 public void requestParam(int param){ 1228 if (hasActiveTimers()){ 1229 return; 1230 } 1231 bootState = BootState.GET_PARAMS; 1232 setAllParamTimeout(); 1233 send.rQNPN(nodeNumber, param); 1234 } 1235 1236 1237 /** 1238 * See if any timers are running, ie waiting for a response from a physical Node. 1239 * 1240 * @return true if timers are running else false 1241 */ 1242 protected boolean hasActiveTimers() { 1243 return allParamTask != null 1244 || startBootTask != null 1245 || checkBootTask != null 1246 || devIdTask != null 1247 || bootIdTask != null 1248 || pauseTask != null 1249 || dataTask != null 1250 || ackTask != null 1251 || checkTask != null; 1252 } 1253 1254 1255 private TimerTask allParamTask; 1256 private TimerTask startBootTask; 1257 private TimerTask checkBootTask; 1258 private TimerTask pauseTask; 1259 private TimerTask dataTask; 1260 private TimerTask ackTask; 1261 private TimerTask devIdTask; 1262 private TimerTask bootIdTask; 1263 private TimerTask checkTask; 1264 1265 // Set when the pane is disposed so that timer tasks that fire late, or 1266 // are already running, do not act on the disposed pane 1267 private volatile boolean disposed = false; 1268 1269 1270 /** 1271 * Cancel and forget all timers 1272 */ 1273 private void cancelAllTimers() { 1274 clearAllParamTimeout(); 1275 clearStartBootTimeout(); 1276 clearCheckBootTimeout(); 1277 clearDevIdTimeout(); 1278 clearBootIdTimeout(); 1279 clearPauseTimeout(); 1280 clearDataTimeout(); 1281 clearAckTimeout(); 1282 clearCheckTimeout(); 1283 } 1284 1285 1286 /** 1287 * Stop timer for a single parameter fetch 1288 */ 1289 private void clearAllParamTimeout() { 1290 if (allParamTask != null) { 1291 allParamTask.cancel(); 1292 allParamTask = null; 1293 } 1294 } 1295 1296 1297 /** 1298 * Start timer for a Parameter request 1299 * 1300 * On timeout, attempt to find module already in boot mode. 1301 */ 1302 private void setAllParamTimeout() { 1303 clearAllParamTimeout(); // resets if timer already running 1304 allParamTask = new TimerTask() { 1305 @Override 1306 public void run() { 1307 allParamTask = null; 1308 if (disposed) { 1309 return; 1310 } 1311 if (busyDialog != null) { 1312 busyDialog.finish(); 1313 busyDialog = null; 1314 log.debug("Failed to read module parameters from node {}", nodeNumber); 1315 hardwareParams.setValid(false); 1316 moduleCheckBox.setSelected(true); 1317 openFileChooserButton.setEnabled(true); 1318 endProgramming(BootStatus.PARAMETER_TIMEOUT); 1319 } 1320 } 1321 }; 1322 TimerUtil.schedule(allParamTask, CbusNode.SINGLE_MESSAGE_TIMEOUT_TIME); 1323 } 1324 1325 1326 /** 1327 * Stop timer for boot mode entry request 1328 */ 1329 private void clearStartBootTimeout() { 1330 if (startBootTask != null) { 1331 startBootTask.cancel(); 1332 startBootTask = null; 1333 } 1334 } 1335 1336 1337 /** 1338 * Start timer for boot mode entry request 1339 * <p> 1340 * We don't get a response, so timeout is expected, assume module is in boot 1341 * mode and start check for boot mode 1342 */ 1343 private void setStartBootTimeout() { 1344 clearStartBootTimeout(); // resets if timer already running 1345 startBootTask = new TimerTask() { 1346 @Override 1347 public void run() { 1348 startBootTask = null; 1349 if (disposed) { 1350 return; 1351 } 1352 setCheckBootTimeout(); 1353 bootState = BootState.CHECK_BOOT_MODE; 1354 CanMessage m = CbusMessage.getBootTest(0); 1355 tc.sendCanMessage(m, null); 1356 } 1357 }; 1358 TimerUtil.schedule(startBootTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1359 } 1360 1361 1362 /** 1363 * Stop timer for boot mode check 1364 */ 1365 private void clearCheckBootTimeout() { 1366 if (checkBootTask != null) { 1367 checkBootTask.cancel(); 1368 checkBootTask = null; 1369 } 1370 } 1371 1372 1373 /** 1374 * Start timer for boot mode check 1375 */ 1376 private void setCheckBootTimeout() { 1377 clearCheckBootTimeout(); // resets if timer already running 1378 checkBootTask = new TimerTask() { 1379 @Override 1380 public void run() { 1381 checkBootTask = null; 1382 if (disposed) { 1383 return; 1384 } 1385 log.error("Timeout checking for boot mode"); 1386 endProgramming(BootStatus.BOOT_TIMEOUT); 1387 } 1388 }; 1389 TimerUtil.schedule(checkBootTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1390 } 1391 1392 1393 /** 1394 * Stop timer for bootloader device ID request 1395 */ 1396 private void clearDevIdTimeout() { 1397 if (devIdTask != null) { 1398 devIdTask.cancel(); 1399 devIdTask = null; 1400 } 1401 } 1402 1403 1404 /** 1405 * Start timer for bootloader device ID request 1406 * <p> 1407 * If we don't get a response we start programming with the old AN247 protocol. 1408 */ 1409 private void setDevIdTimeout() { 1410 clearDevIdTimeout(); // resets if timer already running 1411 devIdTask = new TimerTask() { 1412 @Override 1413 public void run() { 1414 devIdTask = null; 1415 if (disposed) { 1416 return; 1417 } 1418 bootProtocol = BootProtocol.AN247; 1419 log.debug("Found AN247 bootloader"); 1420 addToLog(Bundle.getMessage("BootIdAn247")); 1421 initialise(); 1422 } 1423 }; 1424 TimerUtil.schedule(devIdTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1425 } 1426 1427 1428 /** 1429 * Stop timer for bootloader ID request 1430 */ 1431 private void clearBootIdTimeout() { 1432 if (bootIdTask != null) { 1433 bootIdTask.cancel(); 1434 bootIdTask = null; 1435 } 1436 } 1437 1438 1439 /** 1440 * Start timer for bootloader ID request 1441 * <p> 1442 */ 1443 private void setBootIdTimeout() { 1444 clearBootIdTimeout(); // resets if timer already running 1445 bootIdTask = new TimerTask() { 1446 @Override 1447 public void run() { 1448 bootIdTask = null; 1449 if (disposed) { 1450 return; 1451 } 1452 protocolError(); 1453 } 1454 }; 1455 TimerUtil.schedule(bootIdTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1456 } 1457 1458 1459 /** 1460 * Stop timer for bootloader reset pause 1461 */ 1462 private void clearPauseTimeout() { 1463 if (pauseTask != null) { 1464 pauseTask.cancel(); 1465 pauseTask = null; 1466 } 1467 } 1468 1469 1470 /** 1471 * Start timer for bootloader reset pause 1472 * <p> 1473 * Special case for Pi-SPROG One AN247 protocol only 1474 * <p> 1475 * No reply so timeout is expected. Initialise to new address for application. 1476 * The init is now sent from writeNextData for AN247. 1477 */ 1478 private void setPauseTimeout() { 1479 clearPauseTimeout(); // resets if timer already running 1480 pauseTask = new TimerTask() { 1481 @Override 1482 public void run() { 1483 pauseTask = null; 1484 if (disposed) { 1485 return; 1486 } 1487 hexForBootloader = false; 1488 log.debug("Start writing at address {}", Integer.toHexString(bootAddress)); 1489 addToLog(MessageFormat.format(Bundle.getMessage("BootStartAddress"), Integer.toHexString(bootAddress))); 1490 bootState = BootState.PROG_DATA; 1491 writeNextData(); 1492 } 1493 }; 1494 TimerUtil.schedule(pauseTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1495 } 1496 1497 1498 /** 1499 * Stop timer for data writes 1500 */ 1501 private void clearDataTimeout() { 1502 if (dataTask != null) { 1503 dataTask.cancel(); 1504 dataTask = null; 1505 } 1506 } 1507 1508 1509 /** 1510 * Start timer for data writes 1511 * 1512 * Only used for AN247 prototocl 1513 * <p> 1514 * No reply so timeout is expected. Send more data. 1515 */ 1516 private void setDataTimeout(int timeout) { 1517 clearDataTimeout(); // resets if timer already running 1518 dataTask = new TimerTask() { 1519 @Override 1520 public void run() { 1521 dataTask = null; 1522 if (disposed) { 1523 return; 1524 } 1525 writeNextData(); 1526 } 1527 }; 1528 TimerUtil.schedule(dataTask, timeout); 1529 } 1530 1531 1532 /** 1533 * Stop timer for ACK timeout 1534 */ 1535 private void clearAckTimeout() { 1536 if (ackTask != null) { 1537 ackTask.cancel(); 1538 ackTask = null; 1539 } 1540 } 1541 1542 1543 /** 1544 * Start timer for ACK timeout 1545 * <p> 1546 * Error condition if no ACK received 1547 */ 1548 private void setAckTimeout() { 1549 clearAckTimeout(); // resets if timer already running 1550 ackTask = new TimerTask() { 1551 @Override 1552 public void run() { 1553 ackTask = null; 1554 if (disposed) { 1555 return; 1556 } 1557 endProgramming(BootStatus.ACK_TIMEOUT); 1558 bootAddress -= 8; 1559 log.error("Timeout waiting for data write ACK at address {}", Integer.toHexString(bootAddress)); 1560 } 1561 }; 1562 TimerUtil.schedule(ackTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1563 } 1564 1565 1566 /** 1567 * Stop timer for checksum verification 1568 */ 1569 private void clearCheckTimeout() { 1570 if (checkTask != null) { 1571 checkTask.cancel(); 1572 checkTask = null; 1573 } 1574 } 1575 1576 1577 /** 1578 * Start timer for checksum verification 1579 */ 1580 private void setCheckTimeout() { 1581 clearCheckTimeout(); // resets if timer already running 1582 checkTask = new TimerTask() { 1583 @Override 1584 public void run() { 1585 checkTask = null; 1586 if (disposed) { 1587 return; 1588 } 1589 endProgramming(BootStatus.CHECKSUM_TIMEOUT); 1590 log.error("Timeout verifying checksum"); 1591 } 1592 }; 1593 TimerUtil.schedule(checkTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1594 } 1595 1596 1597 /** 1598 * Add to boot loader Log 1599 * 1600 * @param boottext String console message 1601 */ 1602 public void addToLog(String boottext){ 1603 ThreadingUtil.runOnGUI( ()->{ 1604 bootConsole.append("\n"+boottext); 1605 }); 1606 } 1607 1608 1609 /** 1610 * disconnect from the CBUS 1611 */ 1612 @Override 1613 public void dispose() { 1614 disposed = true; 1615 // stop timers if running 1616 cancelAllTimers(); 1617 if (busyDialog != null) { 1618 busyDialog.finish(); 1619 busyDialog = null; 1620 } 1621 if (hexFile != null) { 1622 hexFile.dispose(); 1623 } 1624 bootConsole.dispose(); 1625 if (tc != null) { 1626 tc.removeCanListener(this); 1627 } 1628 } 1629 1630 1631 /** 1632 * Nested class to create one of these using old-style defaults. 1633 */ 1634 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 1635 1636 public Default() { 1637 super(Bundle.getMessage("MenuItemBootloader"), 1638 new jmri.util.swing.sdi.JmriJFrameInterface(), 1639 CbusBootloaderPane.class.getName(), 1640 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 1641 } 1642 } 1643 1644 1645 private static final Logger log = LoggerFactory.getLogger(CbusBootloaderPane.class); 1646 1647}