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 // validateCurrent() is called from synchronised code 278 public void validateCurrent() { 279 String currentRange = "200 - 996"; 280 int validLimit = 996; 281 if (_memo.getSprogVersion().sprogType.sprogType > SprogType.SPROGIIv3) { 282 currentRange = "200 - 2499"; 283 validLimit = 2499; 284 } 285 try { 286 currentLimit = Integer.parseInt(currentTextField.getText()); 287 } 288 catch (NumberFormatException e) { 289 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CurrentLimitDialogString", currentRange), 290 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 291 currentLimit = validLimit; 292 return; 293 } 294 if ((currentLimit > validLimit) || (currentLimit < 200)) { 295 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CurrentLimitDialogString", currentRange), 296 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 297 currentLimit = validLimit; 298 } 299 } 300 301 synchronized public void saveButtonActionPerformed(java.awt.event.ActionEvent e) { 302 SprogMessage saveMsg; 303 int currentLimitForHardware; 304 // Send Current Limit if possible 305 state = State.CURRENTSENT; 306 if (isCurrentLimitPossible()) { 307 validateCurrent(); 308 // Value written is scaled from mA to hardware units 309 currentLimitForHardware = (int) (currentLimit * (1 / sv.sprogType.getCurrentMultiplier())); 310 if (sv.sprogType.sprogType < SprogType.SPROGIIv3) { 311 // Hack for SPROG bug where MSbyte of value must be non-zero 312 currentLimitForHardware += 256; 313 } 314 tmpString = String.valueOf(currentLimitForHardware); 315 saveMsg = new SprogMessage("I " + tmpString); 316 } else { 317 // Else send blank message to kick things off 318 saveMsg = new SprogMessage(" " + tmpString); 319 } 320 nextLine("cmd: \"" + saveMsg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 321 tc.sendSprogMessage(saveMsg, this); 322 323 // Further messages will be sent from state machine 324 } 325 326 // Called from synchronised code 327 public boolean isCurrentLimitPossible() { 328 return sv.hasCurrentLimit(); 329 } 330 331 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 332 // Called from synchronised code 333 public boolean isBlueLineSupportPossible() { 334 return sv.hasBlueLine(); 335 } 336 337 // Called from synchronised code 338 public boolean isFirmwareUnlockPossible() { 339 return sv.hasFirmwareLock(); 340 } 341 342 // Called from synchronised code 343 public boolean isZTCModePossible() { 344 return sv.hasZTCMode(); 345 } 346 347 /** 348 * Handle a SprogVersion notification. 349 * <p> 350 * Decode the SPROG version and populate the console gui appropriately with 351 * the features applicable to the version. 352 * 353 * @param v The SprogVersion being handled 354 */ 355 @Override 356 public synchronized void notifyVersion(SprogVersion v) { 357 SprogMessage msg; 358 sv = v; 359 // Save it for others 360 _memo.setSprogVersion(v); 361 log.debug("Found: {}", sv ); 362 if (sv.sprogType.isSprog() == false) { 363 // Didn't recognize a SPROG so check if it is in boot mode already 364 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("TypeNoSprogPromptFound"), 365 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 366 } else { 367 if ((sv.sprogType.sprogType > SprogType.SPROGIIv3) && (sv.sprogType.sprogType < SprogType.NANO)) { 368 currentTextField.setToolTipText(Bundle.getMessage("CurrentLimitFieldTooltip2500")); 369 } 370 // We know what we're connected to 371 setTitle(title() + " - Connected to " + sv.toString()); 372 373 // Enable blueline & firmware unlock check boxes 374 if (isBlueLineSupportPossible()) { 375 log.debug("Enable blueline check box"); 376 blueCheckBox.setEnabled(true); 377 if (log.isDebugEnabled()) { 378 log.debug("blueCheckBox isEnabled: {}", blueCheckBox.isEnabled() ); 379 } 380 } 381 if (isFirmwareUnlockPossible()) { 382 log.debug("Enable firmware check box"); 383 unlockCheckBox.setEnabled(true); 384 if (log.isDebugEnabled()) { 385 log.debug("unlockCheckBox isEnabled: {}", unlockCheckBox.isEnabled() ); 386 } 387 } 388 389 ztcCheckBox.setEnabled(isZTCModePossible()); 390 391 // Get Current Limit if available 392 if (isCurrentLimitPossible()) { 393 state = State.CURRENTQUERYSENT; 394 msg = new SprogMessage(1); 395 msg.setOpCode('I'); 396 nextLine("cmd: \"" + msg + "\"\n", ""); 397 tc.sendSprogMessage(msg, this); 398 startTimer(); 399 } else { 400 // Set default and get the mode word 401 currentLimit = (int) (SprogConstants.DEFAULT_I * sv.sprogType.getCurrentMultiplier()); 402 currentTextField.setText(String.valueOf(SprogConstants.DEFAULT_I)); 403 //currentField.setValue(Integer.valueOf(SprogConstants.DEFAULT_I)); // TODO use JSpinner so int 404 state = State.MODEQUERYSENT; 405 msg = new SprogMessage(1); 406 msg.setOpCode('M'); 407 nextLine("cmd: \"" + msg + "\"\n", ""); 408 tc.sendSprogMessage(msg, this); 409 startTimer(); 410 } 411 } 412 } 413 414 /** 415 * {@inheritDoc} 416 */ 417 @Override 418 public synchronized void notifyMessage(SprogMessage l) { // receive a message and log it 419 nextLine("cmd: \"" + l.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 420 } 421 422 /** 423 * Handle a SprogReply in a console specific way. 424 * <p> 425 * Parse replies from the SPROG using a state machine to determine what we 426 * are expecting in response to commands sent to the SPROG. Extract data to 427 * populate various fields in the gui. 428 * 429 * @param l The SprogReply to be parsed 430 */ 431 @Override 432 public synchronized void notifyReply(SprogReply l) { // receive a reply message and log it 433 SprogMessage msg; 434 int currentLimitFromHardware; 435 replyString = l.toString(); 436 nextLine("rep: \"" + replyString + "\"\n", ""); 437 438 // *** Check for error reply 439 switch (state) { 440 case IDLE: 441 log.debug("reply in IDLE state: {}", replyString); 442 break; 443 case CURRENTQUERYSENT: 444 // Look for an "I=" reply 445 log.debug("reply in CURRENTQUERYSENT state: {}", replyString); 446 if (replyString.contains("I=")) { 447 stopTimer(); 448 int valueLength = 4; 449 if (sv.sprogType.sprogType >= SprogType.SPROGIIv3) { 450 valueLength = 6; 451 } 452 tmpString = replyString.substring(replyString.indexOf("=") 453 + 1, replyString.indexOf("=") + valueLength); 454 log.debug("Current limit string: {}", tmpString); 455 try { 456 currentLimitFromHardware = Integer.parseInt(tmpString); 457 } 458 catch (NumberFormatException e) { 459 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogLimit"), 460 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 461 state = State.IDLE; 462 return; 463 } 464 // Value written is scaled from hardware units to mA 465 currentLimit = (int) (currentLimitFromHardware * sv.sprogType.getCurrentMultiplier()); 466 log.debug("Current limit scale factor: {}", sv.sprogType.getCurrentMultiplier()); 467 log.debug("Current limit from hardware: {} scaled to: {}mA", currentLimitFromHardware, currentLimit); 468 currentTextField.setText(String.valueOf(currentLimit)); 469 currentTextField.setEnabled(true); 470 471 // Next get the mode word 472 state = State.MODEQUERYSENT; 473 msg = new SprogMessage(1); 474 msg.setOpCode('M'); 475 nextLine("cmd: \"" + msg + "\"\n", ""); 476 tc.sendSprogMessage(msg, this); 477 startTimer(); 478 } 479 break; 480 case MODEQUERYSENT: 481 log.debug("reply in MODEQUERYSENT state: {}", replyString); 482 if (replyString.contains("M=")) { 483 stopTimer(); 484 tmpString = replyString.substring(replyString.indexOf("=") 485 + 2, replyString.indexOf("=") + 6); 486 // Value returned is in hex 487 try { 488 modeWord = Integer.parseInt(tmpString, 16); 489 } 490 catch (NumberFormatException e) { 491 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFrameDialogWord"), 492 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 493 state = State.IDLE; 494 return; 495 } 496 state = State.IDLE; 497 // Set Speed step radio buttons, etc., according to mode word 498 if ((modeWord & SprogConstants.STEP14_BIT) != 0) { 499 speed14Button.setSelected(true); 500 } else if ((modeWord & SprogConstants.STEP28_BIT) != 0) { 501 speed28Button.setSelected(true); 502 } else { 503 speed128Button.setSelected(true); 504 } 505 if ((modeWord & SprogConstants.ZTC_BIT) != 0) { 506 ztcCheckBox.setSelected(true); 507 } 508 if ((modeWord & SprogConstants.BLUE_BIT) != 0) { 509 blueCheckBox.setSelected(true); 510 } 511 } 512 break; 513 case CURRENTSENT: 514 // Any reply will do here 515 log.debug("reply in CURRENTSENT state: {}", replyString); 516 // Get new mode word - assume 128 steps 517 modeWord = SprogConstants.STEP128_BIT; 518 if (speed14Button.isSelected()) { 519 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP14_BIT; 520 } else if (speed28Button.isSelected()) { 521 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP28_BIT; 522 } 523 524 // ZTC mode 525 if (ztcCheckBox.isSelected() == true) { 526 modeWord = modeWord | SprogConstants.ZTC_BIT; 527 } 528 529 // Blueline mode 530 if (blueCheckBox.isSelected() == true) { 531 modeWord = modeWord | SprogConstants.BLUE_BIT; 532 } 533 534 // firmware unlock 535 if (unlockCheckBox.isSelected() == true) { 536 modeWord = modeWord | SprogConstants.UNLOCK_BIT; 537 } 538 539 // Send new mode word 540 state = State.MODESENT; 541 msg = new SprogMessage("M " + modeWord); 542 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 543 tc.sendSprogMessage(msg, this); 544 break; 545 case MODESENT: 546 // Any reply will do here 547 log.debug("reply in MODESENT state: {}", replyString); 548 // Write to EEPROM 549 state = State.WRITESENT; 550 msg = new SprogMessage("W"); 551 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 552 tc.sendSprogMessage(msg, this); 553 break; 554 case WRITESENT: 555 // Any reply will do here 556 log.debug("reply in WRITESENT state: {}", replyString); 557 // All done 558 state = State.IDLE; 559 break; 560 default: 561 log.warn("Unhandled state: {}", state); 562 break; 563 } 564 } 565 566 /** 567 * Internal routine to handle a timeout. 568 */ 569 protected synchronized void timeout() { 570 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("TypeTimeoutTalkingToSPROG"), 571 Bundle.getMessage("Timeout"), JmriJOptionPane.ERROR_MESSAGE); 572 state = State.IDLE; 573 } 574 575 protected int TIMEOUT = 1000; 576 577 javax.swing.Timer timer = null; 578 579 /** 580 * Internal routine to start timer to protect the mode-change. 581 */ 582 protected void startTimer() { 583 restartTimer(TIMEOUT); 584 } 585 586 /** 587 * Internal routine to stop timer, as all is well. 588 */ 589 protected void stopTimer() { 590 if (timer != null) { 591 timer.stop(); 592 } 593 } 594 595 /** 596 * Internal routine to handle timer starts and restarts. 597 * 598 * @param delay milliseconds to delay 599 */ 600 protected void restartTimer(int delay) { 601 if (timer == null) { 602 timer = new javax.swing.Timer(delay, (java.awt.event.ActionEvent e) -> { 603 timeout(); 604 }); 605 } 606 timer.stop(); 607 timer.setInitialDelay(delay); 608 timer.setRepeats(false); 609 timer.start(); 610 } 611 612 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogConsoleFrame.class); 613 614}