001package jmri.jmrix.sprog.console; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Dimension; 006import java.awt.FlowLayout; 007 008import javax.swing.BorderFactory; 009import javax.swing.BoxLayout; 010import javax.swing.ButtonGroup; 011import javax.swing.JCheckBox; 012import javax.swing.JPanel; 013import javax.swing.JRadioButton; 014 015import jmri.jmrix.sprog.SprogConstants; 016import jmri.jmrix.sprog.SprogListener; 017import jmri.jmrix.sprog.SprogMessage; 018import jmri.jmrix.sprog.SprogReply; 019import jmri.jmrix.sprog.SprogSystemConnectionMemo; 020import jmri.jmrix.sprog.SprogTrafficController; 021import jmri.jmrix.sprog.update.SprogType; 022import jmri.jmrix.sprog.update.SprogVersion; 023import jmri.jmrix.sprog.update.SprogVersionListener; 024import jmri.util.swing.JmriJOptionPane; 025 026/** 027 * Frame for Sprog Console 028 * <p> 029 * Updated Jan 2010 by Andrew Berridge - fixed errors caused by trying to send 030 * some commands while slot manager is active 031 * <p> 032 * Updated April 2016 by Andrew Crosland - remove the checks on slot manager 033 * status, implement a timeout and look for the correct replies which may be 034 * delayed by replies for slot manager. 035 * <p> 036 * Refactored, I18N 037 * 038 * @author Andrew Crosland Copyright (C) 2008, 2016 039 */ 040public class SprogConsoleFrame extends jmri.jmrix.AbstractMonFrame implements SprogListener, SprogVersionListener { 041 042 private SprogSystemConnectionMemo _memo = null; 043 // member declarations 044 protected javax.swing.JLabel cmdLabel = new javax.swing.JLabel(); 045 protected javax.swing.JLabel currentLabel = new javax.swing.JLabel(); 046 protected javax.swing.JButton sendButton = new javax.swing.JButton(); 047 protected javax.swing.JButton saveButton = new javax.swing.JButton(); 048 protected javax.swing.JTextField cmdTextField = new javax.swing.JTextField(12); 049 protected javax.swing.JTextField currentTextField = new javax.swing.JTextField(12); 050 051 protected JCheckBox ztcCheckBox = new JCheckBox(); 052 protected JCheckBox blueCheckBox = new JCheckBox(); 053 protected JCheckBox unlockCheckBox = new JCheckBox(); 054 055 protected ButtonGroup speedGroup = new ButtonGroup(); 056 protected javax.swing.JLabel speedLabel = new javax.swing.JLabel(); 057 protected JRadioButton speed14Button = new JRadioButton(Bundle.getMessage("ButtonXStep", 14)); // i18n using shared sprogBundle 058 protected JRadioButton speed28Button = new JRadioButton(Bundle.getMessage("ButtonXStep", 28)); 059 protected JRadioButton speed128Button = new JRadioButton(Bundle.getMessage("ButtonXStep", 128)); 060 061 protected int modeWord; 062 protected int currentLimit = SprogConstants.DEFAULT_I; 063 064 // members for handling the SPROG interface 065 SprogTrafficController tc = null; 066 String replyString; 067 String tmpString = null; 068 State state = State.IDLE; 069 070 SprogVersion sv; 071 072 enum State { 073 074 IDLE, 075 CURRENTQUERYSENT, // awaiting reply to "I" 076 MODEQUERYSENT, // awaiting reply to "M" 077 CURRENTSENT, // awaiting reply to "I xxx" 078 MODESENT, // awaiting reply to "M xxx" 079 WRITESENT // awaiting reply to "W" 080 } 081 082 public SprogConsoleFrame(SprogSystemConnectionMemo memo) { 083 super(); 084 _memo = memo; 085 } 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 protected String title() { 092 return Bundle.getMessage("SprogConsoleTitle"); 093 } 094 095 /** 096 * {@inheritDoc} 097 */ 098 @Override 099 protected void init() { 100 // connect to TrafficController 101 tc = _memo.getSprogTrafficController(); 102 tc.addSprogListener(this); 103 } 104 105 /** 106 * {@inheritDoc} 107 */ 108 @Override 109 public void dispose() { 110 stopTimer(); 111 if (tc != null) { 112 tc.removeSprogListener(this); 113 } 114 super.dispose(); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 121 // Ignore unsynchronized access to state 122 @Override 123 public void initComponents() { 124 //SprogMessage msg; 125 super.initComponents(); 126 127 // Add a nice border to super class 128 super.jScrollPane1.setBorder(BorderFactory.createTitledBorder( 129 BorderFactory.createEtchedBorder(), Bundle.getMessage("CommandHistoryTitle"))); 130 131 // Let user press return to enter message 132 entryField.addActionListener((java.awt.event.ActionEvent e) -> { 133 enterButtonActionPerformed(e); 134 }); 135 136 /* 137 * Command panel 138 */ 139 JPanel cmdPane1 = new JPanel(); 140 cmdPane1.setBorder(BorderFactory.createTitledBorder( 141 BorderFactory.createEtchedBorder(), Bundle.getMessage("SendCommandTitle"))); 142 cmdPane1.setLayout(new FlowLayout()); 143 144 cmdLabel.setText(Bundle.getMessage("CommandLabel")); 145 cmdLabel.setVisible(true); 146 147 sendButton.setText(Bundle.getMessage("ButtonSend")); 148 sendButton.setVisible(true); 149 sendButton.setToolTipText(Bundle.getMessage("SendPacketTooltip")); 150 151 cmdTextField.setText(""); 152 cmdTextField.setToolTipText(Bundle.getMessage("EnterSPROGCommandTooltip", Bundle.getMessage("ButtonSend"))); 153 cmdTextField.setMaximumSize( 154 new Dimension(cmdTextField.getMaximumSize().width, 155 cmdTextField.getPreferredSize().height) 156 ); 157 158 cmdTextField.addActionListener((java.awt.event.ActionEvent e) -> { 159 sendButtonActionPerformed(e); 160 }); 161 162 sendButton.addActionListener((java.awt.event.ActionEvent e) -> { 163 sendButtonActionPerformed(e); 164 }); 165 166 cmdPane1.add(cmdLabel); 167 cmdPane1.add(cmdTextField); 168 cmdPane1.add(sendButton); 169 170 getContentPane().add(cmdPane1); 171 172 /* 173 * Speed Step Panel 174 */ 175 JPanel speedPanel = new JPanel(); 176 speedPanel.setBorder(BorderFactory.createEtchedBorder()); 177 speedLabel.setText(Bundle.getMessage("SpeedStepModeLabel")); 178 speedPanel.add(speedLabel); 179 speedPanel.add(speed14Button); 180 speedPanel.add(speed28Button); 181 speedPanel.add(speed128Button); 182 speedGroup.add(speed14Button); 183 speedGroup.add(speed28Button); 184 speedGroup.add(speed128Button); 185 speed14Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 14)); 186 speed28Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 28)); 187 speed128Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 128)); 188 189 /* 190 * Configuration panel 191 */ 192 JPanel configPanel = new JPanel(); 193 // *** Which versions support current limit ??? 194 currentLabel.setText(Bundle.getMessage("CurrentLimitLabel")); 195 currentLabel.setVisible(true); 196 197 currentTextField.setText(""); 198 currentTextField.setEnabled(false); 199 currentTextField.setToolTipText(Bundle.getMessage("CurrentLimitFieldTooltip")); 200 currentTextField.setMaximumSize( 201 new Dimension(currentTextField.getMaximumSize().width, 202 currentTextField.getPreferredSize().height 203 ) 204 ); 205 206 ztcCheckBox.setText(Bundle.getMessage("ButtonSetZTCMode")); 207 ztcCheckBox.setVisible(true); 208 ztcCheckBox.setToolTipText(Bundle.getMessage("ButtonSetZTCModeTooltip")); 209 210 blueCheckBox.setText(Bundle.getMessage("ButtonSetBluelineMode")); 211 blueCheckBox.setVisible(true); 212 blueCheckBox.setEnabled(false); 213 blueCheckBox.setToolTipText(Bundle.getMessage("ButtonSetBluelineModeTooltip")); 214 215 unlockCheckBox.setText(Bundle.getMessage("ButtonUnlockFirmware")); 216 unlockCheckBox.setVisible(true); 217 unlockCheckBox.setEnabled(false); 218 unlockCheckBox.setToolTipText(Bundle.getMessage("ButtonUnlockFirmwareTooltip")); 219 220 configPanel.add(currentLabel); 221 configPanel.add(currentTextField); 222 configPanel.add(ztcCheckBox); 223 configPanel.add(blueCheckBox); 224 configPanel.add(unlockCheckBox); 225 226 /* 227 * Status Panel 228 */ 229 JPanel statusPanel = new JPanel(); 230 statusPanel.setBorder(BorderFactory.createTitledBorder( 231 BorderFactory.createEtchedBorder(), Bundle.getMessage("ConfigurationTitle"))); 232 statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.Y_AXIS)); 233 234 saveButton.setText(Bundle.getMessage("ButtonApply")); 235 saveButton.setVisible(true); 236 saveButton.setToolTipText(Bundle.getMessage("ButtonApplyTooltip")); 237 238 saveButton.addActionListener((java.awt.event.ActionEvent e) -> { 239 saveButtonActionPerformed(e); 240 }); 241 242 statusPanel.add(speedPanel); 243 statusPanel.add(configPanel); 244 statusPanel.add(saveButton); 245 246 getContentPane().add(statusPanel); 247 248 // pack for display 249 pack(); 250 cmdPane1.setMaximumSize(statusPanel.getSize()); 251 statusPanel.setMaximumSize(statusPanel.getSize()); 252 pack(); 253 254 // Now the GUI is all setup we can get the SPROG version 255 _memo.getSprogVersionQuery().requestVersion(this); 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override 262 protected void setHelp() { 263 addHelpMenu("package.jmri.jmrix.sprog.console.SprogConsoleFrame", true); 264 } 265 266 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 267 SprogMessage m = new SprogMessage(cmdTextField.getText()); 268 // Messages sent by us will not be forwarded back so add to display manually 269 nextLine("cmd: \"" + m.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 270 tc.sendSprogMessage(m, this); 271 } 272 273 /** 274 * Validate the current limit value entered by the user, depending on the 275 * SPROG version. 276 */ 277 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 278 // validateCurrent() is called from synchronised code 279 public void validateCurrent() { 280 String currentRange = "200 - 996"; 281 int validLimit = 996; 282 if (_memo.getSprogVersion().sprogType.sprogType > SprogType.SPROGIIv3) { 283 currentRange = "200 - 2499"; 284 validLimit = 2499; 285 } 286 try { 287 currentLimit = Integer.parseInt(currentTextField.getText()); 288 } 289 catch (NumberFormatException e) { 290 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CurrentLimitDialogString", currentRange), 291 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 292 currentLimit = validLimit; 293 return; 294 } 295 if ((currentLimit > validLimit) || (currentLimit < 200)) { 296 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CurrentLimitDialogString", currentRange), 297 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 298 currentLimit = validLimit; 299 } 300 } 301 302 synchronized public void saveButtonActionPerformed(java.awt.event.ActionEvent e) { 303 SprogMessage saveMsg; 304 int currentLimitForHardware; 305 // Send Current Limit if possible 306 state = State.CURRENTSENT; 307 if (isCurrentLimitPossible()) { 308 validateCurrent(); 309 // Value written is scaled from mA to hardware units 310 currentLimitForHardware = (int) (currentLimit * (1 / sv.sprogType.getCurrentMultiplier())); 311 if (sv.sprogType.sprogType < SprogType.SPROGIIv3) { 312 // Hack for SPROG bug where MSbyte of value must be non-zero 313 currentLimitForHardware += 256; 314 } 315 tmpString = String.valueOf(currentLimitForHardware); 316 saveMsg = new SprogMessage("I " + tmpString); 317 } else { 318 // Else send blank message to kick things off 319 saveMsg = new SprogMessage(" " + tmpString); 320 } 321 nextLine("cmd: \"" + saveMsg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 322 tc.sendSprogMessage(saveMsg, this); 323 324 // Further messages will be sent from state machine 325 } 326 327 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 328 // Called from synchronised code 329 public boolean isCurrentLimitPossible() { 330 return sv.hasCurrentLimit(); 331 } 332 333 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 334 // Called from synchronised code 335 public boolean isBlueLineSupportPossible() { 336 return sv.hasBlueLine(); 337 } 338 339 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 340 // Called from synchronised code 341 public boolean isFirmwareUnlockPossible() { 342 return sv.hasFirmwareLock(); 343 } 344 345 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 346 // Called from synchronised code 347 public boolean isZTCModePossible() { 348 return sv.hasZTCMode(); 349 } 350 351 /** 352 * Handle a SprogVersion notification. 353 * <p> 354 * Decode the SPROG version and populate the console gui appropriately with 355 * the features applicable to the version. 356 * 357 * @param v The SprogVersion being handled 358 */ 359 @Override 360 public synchronized void notifyVersion(SprogVersion v) { 361 SprogMessage msg; 362 sv = v; 363 // Save it for others 364 _memo.setSprogVersion(v); 365 log.debug("Found: {}", sv ); 366 if (sv.sprogType.isSprog() == false) { 367 // Didn't recognize a SPROG so check if it is in boot mode already 368 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("TypeNoSprogPromptFound"), 369 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 370 } else { 371 if ((sv.sprogType.sprogType > SprogType.SPROGIIv3) && (sv.sprogType.sprogType < SprogType.NANO)) { 372 currentTextField.setToolTipText(Bundle.getMessage("CurrentLimitFieldTooltip2500")); 373 } 374 // We know what we're connected to 375 setTitle(title() + " - Connected to " + sv.toString()); 376 377 // Enable blueline & firmware unlock check boxes 378 if (isBlueLineSupportPossible()) { 379 log.debug("Enable blueline check box"); 380 blueCheckBox.setEnabled(true); 381 if (log.isDebugEnabled()) { 382 log.debug("blueCheckBox isEnabled: {}", blueCheckBox.isEnabled() ); 383 } 384 } 385 if (isFirmwareUnlockPossible()) { 386 log.debug("Enable firmware check box"); 387 unlockCheckBox.setEnabled(true); 388 if (log.isDebugEnabled()) { 389 log.debug("unlockCheckBox isEnabled: {}", unlockCheckBox.isEnabled() ); 390 } 391 } 392 393 ztcCheckBox.setEnabled(isZTCModePossible()); 394 395 // Get Current Limit if available 396 if (isCurrentLimitPossible()) { 397 state = State.CURRENTQUERYSENT; 398 msg = new SprogMessage(1); 399 msg.setOpCode('I'); 400 nextLine("cmd: \"" + msg + "\"\n", ""); 401 tc.sendSprogMessage(msg, this); 402 startTimer(); 403 } else { 404 // Set default and get the mode word 405 currentLimit = (int) (SprogConstants.DEFAULT_I * sv.sprogType.getCurrentMultiplier()); 406 currentTextField.setText(String.valueOf(SprogConstants.DEFAULT_I)); 407 //currentField.setValue(Integer.valueOf(SprogConstants.DEFAULT_I)); // TODO use JSpinner so int 408 state = State.MODEQUERYSENT; 409 msg = new SprogMessage(1); 410 msg.setOpCode('M'); 411 nextLine("cmd: \"" + msg + "\"\n", ""); 412 tc.sendSprogMessage(msg, this); 413 startTimer(); 414 } 415 } 416 } 417 418 /** 419 * {@inheritDoc} 420 */ 421 @Override 422 public synchronized void notifyMessage(SprogMessage l) { // receive a message and log it 423 nextLine("cmd: \"" + l.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 424 } 425 426 /** 427 * Handle a SprogReply in a console specific way. 428 * <p> 429 * Parse replies from the SPROG using a state machine to determine what we 430 * are expecting in response to commands sent to the SPROG. Extract data to 431 * populate various fields in the gui. 432 * 433 * @param l The SprogReply to be parsed 434 */ 435 @Override 436 public synchronized void notifyReply(SprogReply l) { // receive a reply message and log it 437 SprogMessage msg; 438 int currentLimitFromHardware; 439 replyString = l.toString(); 440 nextLine("rep: \"" + replyString + "\"\n", ""); 441 442 // *** Check for error reply 443 switch (state) { 444 case IDLE: 445 log.debug("reply in IDLE state: {}", replyString); 446 break; 447 case CURRENTQUERYSENT: 448 // Look for an "I=" reply 449 log.debug("reply in CURRENTQUERYSENT state: {}", replyString); 450 if (replyString.contains("I=")) { 451 stopTimer(); 452 int valueLength = 4; 453 if (sv.sprogType.sprogType >= SprogType.SPROGIIv3) { 454 valueLength = 6; 455 } 456 tmpString = replyString.substring(replyString.indexOf("=") 457 + 1, replyString.indexOf("=") + valueLength); 458 log.debug("Current limit string: {}", tmpString); 459 try { 460 currentLimitFromHardware = Integer.parseInt(tmpString); 461 } 462 catch (NumberFormatException e) { 463 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogLimit"), 464 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 465 state = State.IDLE; 466 return; 467 } 468 // Value written is scaled from hardware units to mA 469 currentLimit = (int) (currentLimitFromHardware * sv.sprogType.getCurrentMultiplier()); 470 log.debug("Current limit scale factor: {}", sv.sprogType.getCurrentMultiplier()); 471 log.debug("Current limit from hardware: {} scaled to: {}mA", currentLimitFromHardware, currentLimit); 472 currentTextField.setText(String.valueOf(currentLimit)); 473 currentTextField.setEnabled(true); 474 475 // Next get the mode word 476 state = State.MODEQUERYSENT; 477 msg = new SprogMessage(1); 478 msg.setOpCode('M'); 479 nextLine("cmd: \"" + msg + "\"\n", ""); 480 tc.sendSprogMessage(msg, this); 481 startTimer(); 482 } 483 break; 484 case MODEQUERYSENT: 485 log.debug("reply in MODEQUERYSENT state: {}", replyString); 486 if (replyString.contains("M=")) { 487 stopTimer(); 488 tmpString = replyString.substring(replyString.indexOf("=") 489 + 2, replyString.indexOf("=") + 6); 490 // Value returned is in hex 491 try { 492 modeWord = Integer.parseInt(tmpString, 16); 493 } 494 catch (NumberFormatException e) { 495 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogWord"), 496 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 497 state = State.IDLE; 498 return; 499 } 500 state = State.IDLE; 501 // Set Speed step radio buttons, etc., according to mode word 502 if ((modeWord & SprogConstants.STEP14_BIT) != 0) { 503 speed14Button.setSelected(true); 504 } else if ((modeWord & SprogConstants.STEP28_BIT) != 0) { 505 speed28Button.setSelected(true); 506 } else { 507 speed128Button.setSelected(true); 508 } 509 if ((modeWord & SprogConstants.ZTC_BIT) != 0) { 510 ztcCheckBox.setSelected(true); 511 } 512 if ((modeWord & SprogConstants.BLUE_BIT) != 0) { 513 blueCheckBox.setSelected(true); 514 } 515 } 516 break; 517 case CURRENTSENT: 518 // Any reply will do here 519 log.debug("reply in CURRENTSENT state: {}", replyString); 520 // Get new mode word - assume 128 steps 521 modeWord = SprogConstants.STEP128_BIT; 522 if (speed14Button.isSelected()) { 523 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP14_BIT; 524 } else if (speed28Button.isSelected()) { 525 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP28_BIT; 526 } 527 528 // ZTC mode 529 if (ztcCheckBox.isSelected() == true) { 530 modeWord = modeWord | SprogConstants.ZTC_BIT; 531 } 532 533 // Blueline mode 534 if (blueCheckBox.isSelected() == true) { 535 modeWord = modeWord | SprogConstants.BLUE_BIT; 536 } 537 538 // firmware unlock 539 if (unlockCheckBox.isSelected() == true) { 540 modeWord = modeWord | SprogConstants.UNLOCK_BIT; 541 } 542 543 // Send new mode word 544 state = State.MODESENT; 545 msg = new SprogMessage("M " + modeWord); 546 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 547 tc.sendSprogMessage(msg, this); 548 break; 549 case MODESENT: 550 // Any reply will do here 551 log.debug("reply in MODESENT state: {}", replyString); 552 // Write to EEPROM 553 state = State.WRITESENT; 554 msg = new SprogMessage("W"); 555 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 556 tc.sendSprogMessage(msg, this); 557 break; 558 case WRITESENT: 559 // Any reply will do here 560 log.debug("reply in WRITESENT state: {}", replyString); 561 // All done 562 state = State.IDLE; 563 break; 564 default: 565 log.warn("Unhandled state: {}", state); 566 break; 567 } 568 } 569 570 /** 571 * Internal routine to handle a timeout. 572 */ 573 protected synchronized void timeout() { 574 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("TypeTimeoutTalkingToSPROG"), 575 Bundle.getMessage("Timeout"), JmriJOptionPane.ERROR_MESSAGE); 576 state = State.IDLE; 577 } 578 579 protected int TIMEOUT = 1000; 580 581 javax.swing.Timer timer = null; 582 583 /** 584 * Internal routine to start timer to protect the mode-change. 585 */ 586 protected void startTimer() { 587 restartTimer(TIMEOUT); 588 } 589 590 /** 591 * Internal routine to stop timer, as all is well. 592 */ 593 protected void stopTimer() { 594 if (timer != null) { 595 timer.stop(); 596 } 597 } 598 599 /** 600 * Internal routine to handle timer starts and restarts. 601 * 602 * @param delay milliseconds to delay 603 */ 604 protected void restartTimer(int delay) { 605 if (timer == null) { 606 timer = new javax.swing.Timer(delay, (java.awt.event.ActionEvent e) -> { 607 timeout(); 608 }); 609 } 610 timer.stop(); 611 timer.setInitialDelay(delay); 612 timer.setRepeats(false); 613 timer.start(); 614 } 615 616 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogConsoleFrame.class); 617 618}