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 slowWrite.addActionListener(writeSpeedListener); 422 423 424 425 return menuList; 426 } 427 428 429 /** 430 * Get the delay to be inserted between bootloader data writes. 431 * 432 * For AN247, that has no handshaking can be slow or fast and then extended 433 * for slow writes to EEPROM and CONFIG. 434 * 435 * Only a single long timeout is used for CBUS protocol, which has full 436 * handshaking 437 * 438 * @return Delay in ms 439 */ 440 int getWriteDelay() { 441 int delay = CbusNode.BOOT_PROG_TIMEOUT_FAST; 442 443 if (bootProtocol == BootProtocol.AN247) { 444 if (slowWrite.isSelected()) { 445 delay = CbusNode.BOOT_PROG_TIMEOUT_SLOW; 446 } 447 if (bootAddress >= CONFIG_START) { 448 delay *= 8; 449 } 450 } else { 451 delay = CbusNode.BOOT_LONG_TIMEOUT_TIME; 452 } 453 454 return delay; 455 } 456 457 458 /** 459 * Kick off the reading of parameters from the node, starting with parameter 460 * 0, the number of parameters 461 * 462 * @param e 463 */ 464 private void readNodeParamsButtonActionPerformed(java.awt.event.ActionEvent e) { 465 try { 466 nodeNumber = Integer.parseInt(nodeNumberField.getText()); 467 } catch (NumberFormatException e1) { 468 addToLog(Bundle.getMessage("BootInvalidNode")); 469 log.error("Invalid node number {}", nodeNumberField.getText()); 470 return; 471 } 472 // Read the parameters from the chosen node 473 addToLog(Bundle.getMessage("BootReadingParams")); 474 hardwareParams = new CbusParameters(); 475 nextParam = 0; 476 busyDialog = new BusyDialog(topFrame, Bundle.getMessage("BootReadingParams"), false); 477 busyDialog.start(); 478 requestParam(nextParam); 479 } 480 481 482 /** 483 * Let the user choose the hex file and check that it is suitable for the 484 * selected node. 485 * 486 * @param e 487 */ 488 private void openFileChooserButtonActionPerformed(java.awt.event.ActionEvent e) { 489 // start at current file, show dialog 490 int retVal = hexFileChooser.showOpenDialog(this); 491 // handle selection or cancel 492 if (retVal == JFileChooser.APPROVE_OPTION) { 493 hexFile = new CbusPicHexFile(hexFileChooser.getSelectedFile().getPath()); 494 log.debug("hex file chosen: {}", hexFile.getName()); 495 addToLog(MessageFormat.format(Bundle.getMessage("BootFileChosen"), hexFile.getName())); 496 try { 497 hexFile.openRd(); 498 hexFile.read(); 499 } catch (IOException ex) { 500 log.error("Error opening hex file"); 501 addToLog(Bundle.getMessage("BootHexFileOpenFailed")); 502 return; 503 } 504 505 fileParams = hexFile.getParams(); 506 if (!moduleCheckBox.isSelected()) { 507 if (fileParams.validate(fileParams, hardwareParams)) { 508 addToLog(MessageFormat.format(Bundle.getMessage("BootHexFileFoundParameters"), fileParams.toString())); 509 addToLog(Bundle.getMessage("BootHexFileParametersMatch")); 510 programButton.setEnabled(true); 511 } else { 512 addToLog(Bundle.getMessage("BootHexFileParametersMismatch")); 513 } 514 } else { 515 addToLog(Bundle.getMessage("BootHexFileIgnoringParameters")); 516 programButton.setEnabled(true); 517 } 518 if ((hardwareParams.areValid()) && (hardwareParams.getLoadAddress() == 0)) { 519 // Special case of rewriting the bootloader for Pi-SPROG One 520 addToLog(Bundle.getMessage("BootBoot")); 521 hexForBootloader = true; 522 programButton.setEnabled(true); 523 } 524 } 525 } 526 527 528 /** 529 * Send BOOTM OPC to put module in boot mode 530 * 531 * @param e 532 */ 533 private void programButtonActionPerformed(java.awt.event.ActionEvent e) { 534 if (hasActiveTimers()){ 535 return; 536 } 537 openFileChooserButton.setEnabled(false); 538 programButton.setEnabled(false); 539 busyDialog = new BusyDialog(topFrame, Bundle.getMessage("BootLoading"), false); 540 busyDialog.start(); 541 setStartBootTimeout(); 542 bootState = BootState.START_BOOT; 543 CanMessage m = CbusMessage.getBootEntry(nodeNumber, 0); 544 tc.sendCanMessage(m, null); 545 } 546 547 548 /** 549 * Process some outgoing CAN frames 550 * <p> 551 * The CBUS bootloader was originally "fire and forget", with no positive 552 * acknowledgement. We had to wait an indeterminate time and assume the 553 * write was successful. 554 * <p> 555 * A PIC based node will halt execution for some time ((10+ ms with newer Q 556 * series devices) whilst FLASH operations (erase and/or write) complete, 557 * during which time I/O will not be serviced. This is probably OK with CAN 558 * transport, assuming the ECAN continues to accept frames. With serial 559 * (UART) transport, as used by Pi-SPROG, the timing is much more critical 560 * as a single missed character will corrupt the node firmware. 561 * <p> 562 * Furthermore, on some platforms, e.g., Raspberry Pi, there can be 563 * considerable delays between the call to the traffic controller 564 * sendMessage() method and the message being sent by the transmit thread. 565 * This may be due to Flash file system operations and could be affected by 566 * the speed of the SD card. Once the message leaves the transmit thread, we 567 * are at the mercy of the underlying OS, where there can be further delays. 568 * <p> 569 * We could set an overlong timeout, but that would slow down the bootloading 570 * process in all cases. 571 * <p> 572 * To improve things somewhat we wait until the message has definitely 573 * reached the TC transmit thread, by looking for bootloader data write 574 * messages here. Testing indicates this is a marked improvement with no 575 * failures observed. 576 * 577 * This is unnecessary, and not used, for the new protocol which has a 578 * positive acknowledge mechanism. 579 * 580 * @param m CanMessage 581 */ 582 @Override 583 public void message(CanMessage m) { 584 if (bootProtocol == BootProtocol.AN247) { 585 if ((bootState == BootState.PROG_DATA)) { 586 if (m.isExtended() ) { 587 if (CbusMessage.isBootWriteData(m)) { 588 log.debug("Boot data write message {}", m); 589 setDataTimeout(dataTimeout); 590 } 591 } 592 } 593 } 594 } 595 596 597 /** 598 * Processes incoming CAN replies 599 * <p> 600 * The bootloader is only interested in standard parameter responses and 601 * extended bootloader responses. 602 * 603 * {@inheritDoc} 604 */ 605 @Override 606 public void reply(CanReply r) { 607 608 if ( r.isRtr() ) { 609 return; 610 } 611 612 if (!r.isExtended() ) { 613 log.debug("Standard Reply {}", r); 614 615 handleStandardReply(r); 616 } else { 617// log.debug("Extended Reply {} in state {}", r, bootState); 618 // Extended messages are only used by the bootloader 619 620 handleExtendedReply(r); 621 } 622 } 623 624 625 /** 626 * Handle standard ID CAN replies 627 * 628 * @param r Can reply 629 */ 630 private void handleStandardReply(CanReply r) { 631 int opc = CbusMessage.getOpcode(r); 632 if (bootState != BootState.GET_PARAMS) { 633 log.debug("Reply not for me"); 634 return; 635 } 636 637 if ( opc == CbusConstants.CBUS_PARAN) { // response from node 638 clearAllParamTimeout(); 639 640 hardwareParams.setParam(r.getElement(3), r.getElement(4)); 641 if (++nextParam < (hardwareParams.getParam(0) + 1)) { 642 // Read next 643 requestParam(nextParam); 644 } else { 645 // Done reading 646 hardwareParams.setValid(true); 647 addToLog(MessageFormat.format(Bundle.getMessage("BootNodeParametersFinished"), hardwareParams.toString())); 648 busyDialog.finish(); 649 busyDialog = null; 650 openFileChooserButton.setEnabled(true); 651 bootState = BootState.IDLE; 652 } 653 } else { 654 // ignoring OPC 655 } 656 } 657 658 659 /** 660 * Handle extended ID CAN replies 661 * <p> 662 * Handle the reply in the bootloader state machine. 663 * 664 * @param r Can reply 665 */ 666 private void handleExtendedReply(CanReply r) { 667 switch (bootState) { 668 default: 669 break; 670 671 case CHECK_BOOT_MODE: 672 clearCheckBootTimeout(); 673 if (CbusMessage.isBootConfirm(r)) { 674 // The node is in boot mode so we can look for the device ID 675 requestDevId(); 676 } 677 break; 678 679 case WAIT_BOOT_DEVID: 680 clearDevIdTimeout(); 681 if (CbusMessage.isBootDevId(r)) { 682 // We had a response to the Device ID request so we can proceed with the new protocol 683 showDevId(r); 684 bootProtocol = BootProtocol.CBUS_2_0; 685 requestBootId(); 686 } else { 687 protocolError(); 688 } 689 break; 690 691 case WAIT_BOOT_ID: 692 clearBootIdTimeout(); 693 if (CbusMessage.isBootId(r)) { 694 // We had a response to the bootloader ID request so send the write enables 695 showBootId(r); 696 sendBootEnables(); 697 } else { 698 protocolError(); 699 } 700 break; 701 702 case ENABLES_SENT: 703 clearAckTimeout(); 704 if (CbusMessage.isBootOK(r)) { 705 // We had a response to the enables so start programming. 706 initialise(); 707 } else { 708 protocolError(); 709 } 710 break; 711 712 case INIT_SENT: 713 clearAckTimeout(); 714 if (CbusMessage.isBootOK(r)) { 715 // We had a response to the initislise so start programming. 716 writeNextData(); 717 } else if (CbusMessage.isBootOutOfRange(r)) { 718 log.error("INIT Address out of range"); 719 endProgramming(BootStatus.INIT_OUT_OF_RANGE); 720 } else { 721 protocolError(); 722 } 723 break; 724 725 case PROG_DATA: 726 clearAckTimeout(); 727 if (CbusMessage.isBootDataOK(r)) { 728 // Acknowledge received for CBUS protocol 729 writeNextData(); 730 } else if (CbusMessage.isBootError(r)){ 731 log.error("Data Error"); 732 endProgramming(BootStatus.DATA_ERROR); 733 } else if (CbusMessage.isBootDataOutOfRange(r)) { 734 log.error("Data Address out of range"); 735 endProgramming(BootStatus.DATA_OUT_OF_RANGE); 736 } else { 737 protocolError(); 738 } 739 break; 740 741 case NOP_SENT: 742 clearAckTimeout(); 743 if (CbusMessage.isBootOK(r)) { 744 // Acknowledge received for NOP 745 bootState = BootState.PROG_DATA; 746 writeNextData(); 747 } else if (CbusMessage.isBootOutOfRange(r)) { 748 log.error("NOP Address out of range"); 749 endProgramming(BootStatus.ADDRESS_OUT_OF_RANGE); 750 } else { 751 protocolError(); 752 } 753 break; 754 755 case CHECK_SENT: 756 clearCheckTimeout(); 757 if (CbusMessage.isBootOK(r)) { 758 sendReset(); 759 } else if (CbusMessage.isBootError(r)) { 760 // Checksum verify failed 761 log.error("Node {} checksum failed", nodeNumber); 762 endProgramming(BootStatus.CHECKSUM_FAILED); 763 } else { 764 protocolError(); 765 } 766 break; 767 } 768 } 769 770 771 /** 772 * Show the device ID 773 * 774 * Manufacturere and device from cbusdefs.h, device ID from the device 775 * 776 * @param r device ID reply 777 */ 778 void showDevId(CanReply r) { 779 log.debug("Found device ID Manu: {} Dev: {} Device ID: {}", 780 r.getElement(1), 781 r.getElement(2), 782 (r.getElement(3)<<24) + (r.getElement(4)<<16) + (r.getElement(5)<<8) + r.getElement(4)); 783 addToLog(MessageFormat.format(Bundle.getMessage("DevIdCbus"), 784 r.getElement(1), 785 r.getElement(2), 786 (r.getElement(3)<<24) + (r.getElement(4)<<16) + (r.getElement(5)<<8) + r.getElement(4))); 787 } 788 789 790 /** 791 * Show the bootloader ID 792 * 793 * Major/Minor version number, checksum algorithm error report capability 794 * 795 * @param r Bootloader ID reply 796 */ 797 void showBootId(CanReply r) { 798 log.debug("Found bootloader Major: {} Minor: {} Algo: {} Reports: {}", 799 r.getElement(1), 800 r.getElement(2), 801 r.getElement(3), 802 r.getElement(4)); 803 addToLog(MessageFormat.format(Bundle.getMessage("BootIdCbus"), 804 r.getElement(1), 805 r.getElement(2), 806 r.getElement(3), 807 r.getElement(4))); 808 } 809 810 811 /** 812 * Send the memory region write enable bit mask for CBUS bootloader protocol 813 */ 814 void sendBootEnables() { 815 int enables = 1; // Prog mem always enabled 816 817 if (eepromCheckBox.isSelected()) { 818 enables |= 2; 819 } 820 if (configCheckBox.isSelected()) { 821 enables |= 4; 822 } 823 824 bootState = BootState.ENABLES_SENT; 825 setAckTimeout(); 826 CanMessage m = CbusMessage.getBootEnables(enables, 0); 827 log.debug("Send boot enables {}", enables); 828 addToLog(MessageFormat.format(Bundle.getMessage("BootEnables"), enables)); 829 tc.sendCanMessage(m, null); 830 } 831 832 833 /** 834 * Protocol Error 835 */ 836 void protocolError() { 837 log.error("Bootloader Protocol Error in state {}", bootState.toString()); 838 addToLog(MessageFormat.format(Bundle.getMessage("BootProtocol"), bootState.toString())); 839 endProgramming(BootStatus.PROTOCOL_ERROR); 840 } 841 842 843 /** 844 * Is Programming Needed 845 * 846 * Check if any data bytes actually need programming 847 * 848 * @param d data bytes to check 849 * @return false if all bytes are 0xFF, else true 850 */ 851 boolean isProgrammingNeeded(byte [] d) { 852 for (int i = 0; i < d.length; i++) { 853 if (d[i] != (byte)0xFF) { 854 return true; 855 } 856 } 857 return false; 858 } 859 860 861 protected void logFrame(CanMessage m) { 862 log.debug("Write frame {} at address {} {}", dataFramesSent, Integer.toHexString(bootAddress), m); 863 if ((bootAddress & 0xFF) == 0) { 864 addToLog(MessageFormat.format(Bundle.getMessage("BootAddress"), Integer.toHexString(bootAddress))); 865 } else { 866 bootConsole.append("."); 867 } 868 } 869 870 871 /** 872 * Check if data is filtered (e.g., EEPROM selection unticked) 873 * 874 * Used only for AN247 875 * 876 * @param address of data record 877 * @return true if data is filtered and should not be written 878 */ 879 protected boolean dataIsFiltered(int address) { 880 if ((address >= 0x300000) && (address < 0x310000) && (!configCheckBox.isSelected())) { 881 // PIC18 Config space at 0x200000 is filtered 882 return true; 883 } else if ((address >= 0x310000) && (!eepromCheckBox.isSelected())) { 884 // PIC18 EEPROM space at 0x300000, 0x310000 or 0x380000 is filtered 885 return true; 886 } 887 return false; 888 } 889 890 891 /** 892 * Send data to the hardware and keep a running checksum 893 * 894 * @param timeout timeout for write operation 895 */ 896 protected void sendData(int timeout) { 897 898 byte [] d = getDataFromRecord(); 899 dataFramesSent++; 900 901 CanMessage m = CbusMessage.getBootWriteData(d, 0); 902 if (bootProtocol == BootProtocol.CBUS_2_0) { 903 setAckTimeout(); 904 updateChecksum(d); 905 logFrame(m); 906 tc.sendCanMessage(m, null); 907 } else { 908 // For AN247 protocol, we need to filter data 909 if (!dataIsFiltered(bootAddress)) { 910 // Timeout will be set when we see the outgoing message 911 dataTimeout = timeout; 912 updateChecksum(d); 913 logFrame(m); 914 tc.sendCanMessage(m, null); 915 } else { 916 // No data to send, set short timeout to trigger next data 917 setDataTimeout(10); 918 } 919 } 920 bootAddress += d.length; 921 } 922 923 924 /** 925 * Extract data from the current hex record 926 * 927 * Returns 8 byte array or whatever is left in the record if less than 8 bytes. 928 * 929 * Sets recordDone flag if record is exhausted. 930 * 931 * @return data array 932 */ 933 private byte [] getDataFromRecord() { 934 byte [] d; 935 936 if (currentRecord.len - recordIndex >= 8) { 937 d = new byte[8]; 938 if (currentRecord.len - recordIndex == 8) { 939 recordDone = true; 940 } 941 } else { 942 d = new byte[currentRecord.len - recordIndex]; 943 recordDone = true; 944 } 945 for (int i = 0; i < d.length; i++) { 946 d[i] = currentRecord.getData(recordIndex++); 947 } 948 return d; 949 } 950 951 952 /** 953 * Write next data for AN247 protocol 954 */ 955 void writeNextDataAn247() { 956// log.debug("writeNextDataAn247()"); 957 958 if ((bootAddress == 0x7f8) && (hexForBootloader == true)) { 959 log.debug("Pause for bootloader reset"); 960 // Special case for Pi-SPROG One, pause at end of bootloader code to allow time for node to reset 961 bootAddress = 0x800; 962 checksum = 0; 963 bootState = BootState.PROG_PAUSE; 964 setPauseTimeout(); 965 } else { 966 // If the address has skipped we need to send a new address to the bootloader 967 // There's no ACK so send data immediately afterwards 968 if ((currentRecord.address + recordIndex) != bootAddress) { 969 bootAddress = currentRecord.address; 970 // Send NOP to adjust the address, no reply to this from AN247 971 log.debug("Start writing at new address {}", Integer.toHexString(bootAddress)); 972 addToLog(MessageFormat.format(Bundle.getMessage("BootNewAddress"), Integer.toHexString(bootAddress))); 973 CanMessage m = CbusMessage.getBootNop(bootAddress, 0); 974 tc.sendCanMessage(m, null); 975 } 976 if ((bootAddress < CONFIG_START) && (currentRecord.len%8 != 0)) { 977 // AN247 bootloader always writes 8 bytes to FLASH so we need to pad the packet and adjust the length 978 int pad = 8 - currentRecord.len%8; 979 for (int i = 0; i < pad; i++) { 980 currentRecord.data[currentRecord.len + pad] = (byte)0xFF; 981 } 982 currentRecord.len += pad; 983 } 984 sendData(getWriteDelay()); 985 } 986 } 987 988 989 /** 990 * Write next data for CBUS protocol 991 */ 992 void writeNextDataCbus() { 993// log.debug("writeNextDataCbus()"); 994 995 // If the address has skipped we need to send a new address to the bootloader 996 if ((currentRecord.address + recordIndex) != bootAddress) { 997 bootAddress = currentRecord.address; 998 // Send NOP to adjust the address 999 log.debug("Start writing at new address {}", Integer.toHexString(bootAddress)); 1000 addToLog(MessageFormat.format(Bundle.getMessage("BootNewAddress"), Integer.toHexString(bootAddress))); 1001 bootState = BootState.NOP_SENT; 1002 setAckTimeout(); 1003 CanMessage m = CbusMessage.getBootNop(bootAddress, 0); 1004 tc.sendCanMessage(m, null); 1005 } else { 1006 // Extract the data, send it and update bootAddress for next packet 1007 sendData(getWriteDelay()); 1008 } 1009 } 1010 1011 1012 /** 1013 * Write the next data frame for the bootloader 1014 */ 1015 void writeNextData() { 1016 if (recordDone) { 1017 // Current record is exhausted, Get next ONE 1018 recordDone = false; 1019 recordIndex = 0; 1020 currentRecord = hexFile.getNextRecord(); 1021 if (currentRecord.type == HexRecord.END) { 1022 // No more data to send so send checksum 1023 bootState = BootState.CHECK_SENT; 1024 addToLog(Bundle.getMessage("BootVerifyChecksum")); 1025 log.debug("Sending checksum {} as 2s complement {}", checksum, 0 - checksum); 1026 setCheckTimeout(); 1027 CanMessage m = CbusMessage.getBootCheck(0 - checksum, 0); 1028 tc.sendCanMessage(m, null); 1029 return; 1030 } 1031 } 1032 1033 bootState = BootState.PROG_DATA; 1034 if (bootProtocol == BootProtocol.AN247) { 1035 writeNextDataAn247(); 1036 } else { 1037 writeNextDataCbus(); 1038 } 1039 } 1040 1041 1042 /** 1043 * Initialise programming 1044 * 1045 * We normally start at the address from the module parameters, or from the 1046 * hex file, otherwise start at the beginning of the hex file. 1047 */ 1048 private void initialise() { 1049 Optional<HexRecord> hexRecord; 1050 1051 if (hardwareParams.areValid()) { 1052 bootAddress = hardwareParams.getLoadAddress(); 1053 } else if (fileParams.areValid()) { 1054 bootAddress = fileParams.getLoadAddress(); 1055 } else { 1056 bootAddress = hexFile.getProgStart(); 1057 } 1058 1059 recordDone = false; 1060 recordIndex = 0; 1061 1062 hexRecord = hexFile.getRecordForAddress(bootAddress); 1063 if (hexRecord.isPresent()) { 1064 currentRecord = hexRecord.get(); 1065 } else { 1066 log.error("Did not find hex record for load address {}", "0x"+Integer.toHexString(bootAddress)); 1067 endProgramming(BootStatus.ADDRESS_NOT_FOUND); 1068 } 1069 checksum = 0; 1070 dataFramesSent = 0; 1071 log.debug("Initialise at address {}", "0x"+Integer.toHexString(bootAddress)); 1072 addToLog(MessageFormat.format(Bundle.getMessage("BootStartAddress"), Integer.toHexString(bootAddress))); 1073 // Initialise the bootloader, only CBUS protocol will ACK this 1074 if (bootProtocol == BootProtocol.CBUS_2_0) { 1075 setAckTimeout(); 1076 } 1077 CanMessage m = CbusMessage.getBootInitialise(bootAddress, 0); 1078 bootState = BootState.INIT_SENT; 1079 tc.sendCanMessage(m, null); 1080 if (bootProtocol == BootProtocol.AN247) { 1081 // No wait for ACK so start sending data 1082 writeNextData(); 1083 } 1084 } 1085 1086 1087 protected void requestDevId() { 1088 CanMessage m = CbusMessage.getBootDevId(0); 1089 log.debug("Requesting bootloader device ID..."); 1090 addToLog(Bundle.getMessage("ReqDevId")); 1091 bootState = BootState.WAIT_BOOT_DEVID; 1092 setDevIdTimeout(); 1093 tc.sendCanMessage(m, null); 1094 } 1095 1096 1097 protected void requestBootId() { 1098 CanMessage m = CbusMessage.getBootId(0); 1099 log.debug("Requesting bootloader ID..."); 1100 addToLog(Bundle.getMessage("ReqBootId")); 1101 bootState = BootState.WAIT_BOOT_ID; 1102 setBootIdTimeout(); 1103 tc.sendCanMessage(m, null); 1104 } 1105 1106 1107 /** 1108 * Send bootloader reset frame to put the node back into operating mode. 1109 * 1110 * There will be no reply to this. 1111 */ 1112 protected void sendReset() { 1113 CanMessage m = CbusMessage.getBootReset(0); 1114 log.debug("Done. Resetting node..."); 1115 addToLog(Bundle.getMessage("BootFinished")); 1116 tc.sendCanMessage(m, null); 1117 endProgramming(BootStatus.COMPLETE); 1118 } 1119 1120 1121 /** 1122 * Tidy up after programming success or failure 1123 */ 1124 private void endProgramming(BootStatus status) { 1125 log.debug("Boot status is {}", status.toString()); 1126 addToLog(MessageFormat.format(Bundle.getMessage("BootStatus"), status.toString())); 1127 if (busyDialog != null) { 1128 busyDialog.finish(); 1129 busyDialog = null; 1130 } 1131 openFileChooserButton.setEnabled(true); 1132 programButton.setEnabled(false); 1133 bootState = BootState.IDLE; 1134 } 1135 1136 1137 /** 1138 * Add array of bytes to checksum 1139 * 1140 * @param d the array of bytes 1141 */ 1142 protected void updateChecksum(byte [] d) { 1143 for (int i = 0; i < d.length; i++) { 1144 // bytes are signed so Cast to int and take the 8 LSBs 1145 checksum += d[i] & 0xFF; 1146 } 1147 } 1148 1149 1150 /** 1151 * Request a single Parameter from a Physical Node 1152 * <p> 1153 * Will not send the request if there are existing active timers. 1154 * Starts Parameter timeout 1155 * 1156 * @param param Parameter Index Number, Index 0 is total parameters 1157 */ 1158 public void requestParam(int param){ 1159 if (hasActiveTimers()){ 1160 return; 1161 } 1162 bootState = BootState.GET_PARAMS; 1163 setAllParamTimeout(); 1164 send.rQNPN(nodeNumber, param); 1165 } 1166 1167 1168 /** 1169 * See if any timers are running, ie waiting for a response from a physical Node. 1170 * 1171 * @return true if timers are running else false 1172 */ 1173 protected boolean hasActiveTimers() { 1174 return allParamTask != null 1175 || startBootTask != null 1176 || checkBootTask != null 1177 || devIdTask != null 1178 || bootIdTask != null 1179 || pauseTask != null 1180 || dataTask != null 1181 || ackTask != null 1182 || checkTask != null; 1183 } 1184 1185 1186 private TimerTask allParamTask; 1187 private TimerTask startBootTask; 1188 private TimerTask checkBootTask; 1189 private TimerTask pauseTask; 1190 private TimerTask dataTask; 1191 private TimerTask ackTask; 1192 private TimerTask devIdTask; 1193 private TimerTask bootIdTask; 1194 private TimerTask checkTask; 1195 1196 1197 /** 1198 * Stop timer for a single parameter fetch 1199 */ 1200 private void clearAllParamTimeout() { 1201 if (allParamTask != null) { 1202 allParamTask.cancel(); 1203 allParamTask = null; 1204 } 1205 } 1206 1207 1208 /** 1209 * Start timer for a Parameter request 1210 * 1211 * On timeout, attempt to find module already in boot mode. 1212 */ 1213 private void setAllParamTimeout() { 1214 clearAllParamTimeout(); // resets if timer already running 1215 allParamTask = new TimerTask() { 1216 @Override 1217 public void run() { 1218 allParamTask = null; 1219 if (busyDialog != null) { 1220 busyDialog.finish(); 1221 busyDialog = null; 1222 log.debug("Failed to read module parameters from node {}", nodeNumber); 1223 hardwareParams.setValid(false); 1224 moduleCheckBox.setSelected(true); 1225 openFileChooserButton.setEnabled(true); 1226 endProgramming(BootStatus.PARAMETER_TIMEOUT); 1227 } 1228 } 1229 }; 1230 TimerUtil.schedule(allParamTask, CbusNode.SINGLE_MESSAGE_TIMEOUT_TIME); 1231 } 1232 1233 1234 /** 1235 * Stop timer for boot mode entry request 1236 */ 1237 private void clearStartBootTimeout() { 1238 if (startBootTask != null) { 1239 startBootTask.cancel(); 1240 startBootTask = null; 1241 } 1242 } 1243 1244 1245 /** 1246 * Start timer for boot mode entry request 1247 * <p> 1248 * We don't get a response, so timeout is expected, assume module is in boot 1249 * mode and start check for boot mode 1250 */ 1251 private void setStartBootTimeout() { 1252 clearStartBootTimeout(); // resets if timer already running 1253 startBootTask = new TimerTask() { 1254 @Override 1255 public void run() { 1256 startBootTask = null; 1257 setCheckBootTimeout(); 1258 bootState = BootState.CHECK_BOOT_MODE; 1259 CanMessage m = CbusMessage.getBootTest(0); 1260 tc.sendCanMessage(m, null); 1261 } 1262 }; 1263 TimerUtil.schedule(startBootTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1264 } 1265 1266 1267 /** 1268 * Stop timer for boot mode check 1269 */ 1270 private void clearCheckBootTimeout() { 1271 if (checkBootTask != null) { 1272 checkBootTask.cancel(); 1273 checkBootTask = null; 1274 } 1275 } 1276 1277 1278 /** 1279 * Start timer for boot mode check 1280 */ 1281 private void setCheckBootTimeout() { 1282 clearCheckBootTimeout(); // resets if timer already running 1283 checkBootTask = new TimerTask() { 1284 @Override 1285 public void run() { 1286 checkBootTask = null; 1287 log.error("Timeout checking for boot mode"); 1288 endProgramming(BootStatus.BOOT_TIMEOUT); 1289 } 1290 }; 1291 TimerUtil.schedule(checkBootTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1292 } 1293 1294 1295 /** 1296 * Stop timer for bootloader device ID request 1297 */ 1298 private void clearDevIdTimeout() { 1299 if (devIdTask != null) { 1300 devIdTask.cancel(); 1301 devIdTask = null; 1302 } 1303 } 1304 1305 1306 /** 1307 * Start timer for bootloader device ID request 1308 * <p> 1309 * If we don't get a response we start programming with the old AN247 protocol. 1310 */ 1311 private void setDevIdTimeout() { 1312 clearDevIdTimeout(); // resets if timer already running 1313 devIdTask = new TimerTask() { 1314 @Override 1315 public void run() { 1316 devIdTask = null; 1317 bootProtocol = BootProtocol.AN247; 1318 log.debug("Found AN247 bootloader"); 1319 addToLog(Bundle.getMessage("BootIdAn247")); 1320 initialise(); 1321 } 1322 }; 1323 TimerUtil.schedule(devIdTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1324 } 1325 1326 1327 /** 1328 * Stop timer for bootloader ID request 1329 */ 1330 private void clearBootIdTimeout() { 1331 if (bootIdTask != null) { 1332 bootIdTask.cancel(); 1333 bootIdTask = null; 1334 } 1335 } 1336 1337 1338 /** 1339 * Start timer for bootloader ID request 1340 * <p> 1341 */ 1342 private void setBootIdTimeout() { 1343 clearBootIdTimeout(); // resets if timer already running 1344 bootIdTask = new TimerTask() { 1345 @Override 1346 public void run() { 1347 bootIdTask = null; 1348 protocolError(); 1349 } 1350 }; 1351 TimerUtil.schedule(bootIdTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1352 } 1353 1354 1355 /** 1356 * Stop timer for bootloader reset pause 1357 */ 1358 private void clearPauseTimeout() { 1359 if (pauseTask != null) { 1360 pauseTask.cancel(); 1361 pauseTask = null; 1362 } 1363 } 1364 1365 1366 /** 1367 * Start timer for bootloader reset pause 1368 * <p> 1369 * Special case for Pi-SPROG One AN247 protocol only 1370 * <p> 1371 * No reply so timeout is expected. Initialise to new address for application. 1372 * The init is now sent from writeNextData for AN247. 1373 */ 1374 private void setPauseTimeout() { 1375 clearPauseTimeout(); // resets if timer already running 1376 pauseTask = new TimerTask() { 1377 @Override 1378 public void run() { 1379 pauseTask = null; 1380 hexForBootloader = false; 1381 log.debug("Start writing at address {}", Integer.toHexString(bootAddress)); 1382 addToLog(MessageFormat.format(Bundle.getMessage("BootStartAddress"), Integer.toHexString(bootAddress))); 1383 bootState = BootState.PROG_DATA; 1384 writeNextData(); 1385 } 1386 }; 1387 TimerUtil.schedule(pauseTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1388 } 1389 1390 1391 /** 1392 * Stop timer for data writes 1393 */ 1394 private void clearDataTimeout() { 1395 if (dataTask != null) { 1396 dataTask.cancel(); 1397 dataTask = null; 1398 } 1399 } 1400 1401 1402 /** 1403 * Start timer for data writes 1404 * 1405 * Only used for AN247 prototocl 1406 * <p> 1407 * No reply so timeout is expected. Send more data. 1408 */ 1409 private void setDataTimeout(int timeout) { 1410 clearDataTimeout(); // resets if timer already running 1411 dataTask = new TimerTask() { 1412 @Override 1413 public void run() { 1414 dataTask = null; 1415 writeNextData(); 1416 } 1417 }; 1418 TimerUtil.schedule(dataTask, timeout); 1419 } 1420 1421 1422 /** 1423 * Stop timer for ACK timeout 1424 */ 1425 private void clearAckTimeout() { 1426 if (ackTask != null) { 1427 ackTask.cancel(); 1428 ackTask = null; 1429 } 1430 } 1431 1432 1433 /** 1434 * Start timer for ACK timeout 1435 * <p> 1436 * Error condition if no ACK received 1437 */ 1438 private void setAckTimeout() { 1439 clearAckTimeout(); // resets if timer already running 1440 ackTask = new TimerTask() { 1441 @Override 1442 public void run() { 1443 ackTask = null; 1444 endProgramming(BootStatus.ACK_TIMEOUT); 1445 bootAddress -= 8; 1446 log.error("Timeout waiting for data write ACK at address {}", Integer.toHexString(bootAddress)); 1447 } 1448 }; 1449 TimerUtil.schedule(ackTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1450 } 1451 1452 1453 /** 1454 * Stop timer for checksum verification 1455 */ 1456 private void clearCheckTimeout() { 1457 if (checkTask != null) { 1458 checkTask.cancel(); 1459 checkTask = null; 1460 } 1461 } 1462 1463 1464 /** 1465 * Start timer for checksum verification 1466 */ 1467 private void setCheckTimeout() { 1468 clearCheckTimeout(); // resets if timer already running 1469 checkTask = new TimerTask() { 1470 @Override 1471 public void run() { 1472 checkTask = null; 1473 endProgramming(BootStatus.CHECKSUM_TIMEOUT); 1474 log.error("Timeout verifying checksum"); 1475 } 1476 }; 1477 TimerUtil.schedule(checkTask, CbusNode.BOOT_LONG_TIMEOUT_TIME); 1478 } 1479 1480 1481 /** 1482 * Add to boot loader Log 1483 * 1484 * @param boottext String console message 1485 */ 1486 public void addToLog(String boottext){ 1487 ThreadingUtil.runOnGUI( ()->{ 1488 bootConsole.append("\n"+boottext); 1489 }); 1490 } 1491 1492 1493 /** 1494 * disconnect from the CBUS 1495 */ 1496 @Override 1497 public void dispose() { 1498 if (hexFile != null) { 1499 hexFile.dispose(); 1500 } 1501 // stop timers if running 1502 1503 bootConsole.dispose(); 1504 tc.removeCanListener(this); 1505 } 1506 1507 1508 /** 1509 * Nested class to create one of these using old-style defaults. 1510 */ 1511 static public class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 1512 1513 public Default() { 1514 super(Bundle.getMessage("MenuItemBootloader"), 1515 new jmri.util.swing.sdi.JmriJFrameInterface(), 1516 CbusBootloaderPane.class.getName(), 1517 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 1518 } 1519 } 1520 1521 1522 private final static Logger log = LoggerFactory.getLogger(CbusBootloaderPane.class); 1523 1524}