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 if (tc != null) { 111 tc.removeSprogListener(this); 112 } 113 super.dispose(); 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 120 // Ignore unsynchronized access to state 121 @Override 122 public void initComponents() { 123 //SprogMessage msg; 124 super.initComponents(); 125 126 // Add a nice border to super class 127 super.jScrollPane1.setBorder(BorderFactory.createTitledBorder( 128 BorderFactory.createEtchedBorder(), Bundle.getMessage("CommandHistoryTitle"))); 129 130 // Let user press return to enter message 131 entryField.addActionListener((java.awt.event.ActionEvent e) -> { 132 enterButtonActionPerformed(e); 133 }); 134 135 /* 136 * Command panel 137 */ 138 JPanel cmdPane1 = new JPanel(); 139 cmdPane1.setBorder(BorderFactory.createTitledBorder( 140 BorderFactory.createEtchedBorder(), Bundle.getMessage("SendCommandTitle"))); 141 cmdPane1.setLayout(new FlowLayout()); 142 143 cmdLabel.setText(Bundle.getMessage("CommandLabel")); 144 cmdLabel.setVisible(true); 145 146 sendButton.setText(Bundle.getMessage("ButtonSend")); 147 sendButton.setVisible(true); 148 sendButton.setToolTipText(Bundle.getMessage("SendPacketTooltip")); 149 150 cmdTextField.setText(""); 151 cmdTextField.setToolTipText(Bundle.getMessage("EnterSPROGCommandTooltip", Bundle.getMessage("ButtonSend"))); 152 cmdTextField.setMaximumSize( 153 new Dimension(cmdTextField.getMaximumSize().width, 154 cmdTextField.getPreferredSize().height) 155 ); 156 157 cmdTextField.addActionListener((java.awt.event.ActionEvent e) -> { 158 sendButtonActionPerformed(e); 159 }); 160 161 sendButton.addActionListener((java.awt.event.ActionEvent e) -> { 162 sendButtonActionPerformed(e); 163 }); 164 165 cmdPane1.add(cmdLabel); 166 cmdPane1.add(cmdTextField); 167 cmdPane1.add(sendButton); 168 169 getContentPane().add(cmdPane1); 170 171 /* 172 * Speed Step Panel 173 */ 174 JPanel speedPanel = new JPanel(); 175 speedPanel.setBorder(BorderFactory.createEtchedBorder()); 176 speedLabel.setText(Bundle.getMessage("SpeedStepModeLabel")); 177 speedPanel.add(speedLabel); 178 speedPanel.add(speed14Button); 179 speedPanel.add(speed28Button); 180 speedPanel.add(speed128Button); 181 speedGroup.add(speed14Button); 182 speedGroup.add(speed28Button); 183 speedGroup.add(speed128Button); 184 speed14Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 14)); 185 speed28Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 28)); 186 speed128Button.setToolTipText(Bundle.getMessage("ButtonXStepTooltip", 128)); 187 188 /* 189 * Configuration panel 190 */ 191 JPanel configPanel = new JPanel(); 192 // *** Which versions support current limit ??? 193 currentLabel.setText(Bundle.getMessage("CurrentLimitLabel")); 194 currentLabel.setVisible(true); 195 196 currentTextField.setText(""); 197 currentTextField.setEnabled(false); 198 currentTextField.setToolTipText(Bundle.getMessage("CurrentLimitFieldTooltip")); 199 currentTextField.setMaximumSize( 200 new Dimension(currentTextField.getMaximumSize().width, 201 currentTextField.getPreferredSize().height 202 ) 203 ); 204 205 ztcCheckBox.setText(Bundle.getMessage("ButtonSetZTCMode")); 206 ztcCheckBox.setVisible(true); 207 ztcCheckBox.setToolTipText(Bundle.getMessage("ButtonSetZTCModeTooltip")); 208 209 blueCheckBox.setText(Bundle.getMessage("ButtonSetBluelineMode")); 210 blueCheckBox.setVisible(true); 211 blueCheckBox.setEnabled(false); 212 blueCheckBox.setToolTipText(Bundle.getMessage("ButtonSetBluelineModeTooltip")); 213 214 unlockCheckBox.setText(Bundle.getMessage("ButtonUnlockFirmware")); 215 unlockCheckBox.setVisible(true); 216 unlockCheckBox.setEnabled(false); 217 unlockCheckBox.setToolTipText(Bundle.getMessage("ButtonUnlockFirmwareTooltip")); 218 219 configPanel.add(currentLabel); 220 configPanel.add(currentTextField); 221 configPanel.add(ztcCheckBox); 222 configPanel.add(blueCheckBox); 223 configPanel.add(unlockCheckBox); 224 225 /* 226 * Status Panel 227 */ 228 JPanel statusPanel = new JPanel(); 229 statusPanel.setBorder(BorderFactory.createTitledBorder( 230 BorderFactory.createEtchedBorder(), Bundle.getMessage("ConfigurationTitle"))); 231 statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.Y_AXIS)); 232 233 saveButton.setText(Bundle.getMessage("ButtonApply")); 234 saveButton.setVisible(true); 235 saveButton.setToolTipText(Bundle.getMessage("ButtonApplyTooltip")); 236 237 saveButton.addActionListener((java.awt.event.ActionEvent e) -> { 238 saveButtonActionPerformed(e); 239 }); 240 241 statusPanel.add(speedPanel); 242 statusPanel.add(configPanel); 243 statusPanel.add(saveButton); 244 245 getContentPane().add(statusPanel); 246 247 // pack for display 248 pack(); 249 cmdPane1.setMaximumSize(statusPanel.getSize()); 250 statusPanel.setMaximumSize(statusPanel.getSize()); 251 pack(); 252 253 // Now the GUI is all setup we can get the SPROG version 254 _memo.getSprogVersionQuery().requestVersion(this); 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override 261 protected void setHelp() { 262 addHelpMenu("package.jmri.jmrix.sprog.console.SprogConsoleFrame", true); 263 } 264 265 public void sendButtonActionPerformed(java.awt.event.ActionEvent e) { 266 SprogMessage m = new SprogMessage(cmdTextField.getText()); 267 // Messages sent by us will not be forwarded back so add to display manually 268 nextLine("cmd: \"" + m.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 269 tc.sendSprogMessage(m, this); 270 } 271 272 /** 273 * Validate the current limit value entered by the user, depending on the 274 * SPROG version. 275 */ 276 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 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 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 327 // Called from synchronised code 328 public boolean isCurrentLimitPossible() { 329 return sv.hasCurrentLimit(); 330 } 331 332 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 333 // Called from synchronised code 334 public boolean isBlueLineSupportPossible() { 335 return sv.hasBlueLine(); 336 } 337 338 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 339 // Called from synchronised code 340 public boolean isFirmwareUnlockPossible() { 341 return sv.hasFirmwareLock(); 342 } 343 344 @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC") 345 // Called from synchronised code 346 public boolean isZTCModePossible() { 347 return sv.hasZTCMode(); 348 } 349 350 /** 351 * Handle a SprogVersion notification. 352 * <p> 353 * Decode the SPROG version and populate the console gui appropriately with 354 * the features applicable to the version. 355 * 356 * @param v The SprogVersion being handled 357 */ 358 @Override 359 synchronized public void notifyVersion(SprogVersion v) { 360 SprogMessage msg; 361 sv = v; 362 // Save it for others 363 _memo.setSprogVersion(v); 364 log.debug("Found: {}", sv ); 365 if (sv.sprogType.isSprog() == false) { 366 // Didn't recognize a SPROG so check if it is in boot mode already 367 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TypeNoSprogPromptFound"), 368 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 369 } else { 370 if ((sv.sprogType.sprogType > SprogType.SPROGIIv3) && (sv.sprogType.sprogType < SprogType.NANO)) { 371 currentTextField.setToolTipText(Bundle.getMessage("CurrentLimitFieldTooltip2500")); 372 } 373 // We know what we're connected to 374 setTitle(title() + " - Connected to " + sv.toString()); 375 376 // Enable blueline & firmware unlock check boxes 377 if (isBlueLineSupportPossible()) { 378 log.debug("Enable blueline check box"); 379 blueCheckBox.setEnabled(true); 380 if (log.isDebugEnabled()) { 381 log.debug("blueCheckBox isEnabled: {}", blueCheckBox.isEnabled() ); 382 } 383 } 384 if (isFirmwareUnlockPossible()) { 385 log.debug("Enable firmware check box"); 386 unlockCheckBox.setEnabled(true); 387 if (log.isDebugEnabled()) { 388 log.debug("unlockCheckBox isEnabled: {}", unlockCheckBox.isEnabled() ); 389 } 390 } 391 392 ztcCheckBox.setEnabled(isZTCModePossible()); 393 394 // Get Current Limit if available 395 if (isCurrentLimitPossible()) { 396 state = State.CURRENTQUERYSENT; 397 msg = new SprogMessage(1); 398 msg.setOpCode('I'); 399 nextLine("cmd: \"" + msg + "\"\n", ""); 400 tc.sendSprogMessage(msg, this); 401 startTimer(); 402 } else { 403 // Set default and get the mode word 404 currentLimit = (int) (SprogConstants.DEFAULT_I * sv.sprogType.getCurrentMultiplier()); 405 currentTextField.setText(String.valueOf(SprogConstants.DEFAULT_I)); 406 //currentField.setValue(Integer.valueOf(SprogConstants.DEFAULT_I)); // TODO use JSpinner so int 407 state = State.MODEQUERYSENT; 408 msg = new SprogMessage(1); 409 msg.setOpCode('M'); 410 nextLine("cmd: \"" + msg + "\"\n", ""); 411 tc.sendSprogMessage(msg, this); 412 startTimer(); 413 } 414 } 415 } 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override 421 public synchronized void notifyMessage(SprogMessage l) { // receive a message and log it 422 nextLine("cmd: \"" + l.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 423 } 424 425 /** 426 * Handle a SprogReply in a console specific way. 427 * <p> 428 * Parse replies from the SPROG using a state machine to determine what we 429 * are expecting in response to commands sent to the SPROG. Extract data to 430 * populate various fields in the gui. 431 * 432 * @param l The SprogReply to be parsed 433 */ 434 @Override 435 public synchronized void notifyReply(SprogReply l) { // receive a reply message and log it 436 SprogMessage msg; 437 int currentLimitFromHardware; 438 replyString = l.toString(); 439 nextLine("rep: \"" + replyString + "\"\n", ""); 440 441 // *** Check for error reply 442 switch (state) { 443 case IDLE: 444 log.debug("reply in IDLE state: {}", replyString); 445 break; 446 case CURRENTQUERYSENT: 447 // Look for an "I=" reply 448 log.debug("reply in CURRENTQUERYSENT state: {}", replyString); 449 if (replyString.contains("I=")) { 450 stopTimer(); 451 int valueLength = 4; 452 if (sv.sprogType.sprogType >= SprogType.SPROGIIv3) { 453 valueLength = 6; 454 } 455 tmpString = replyString.substring(replyString.indexOf("=") 456 + 1, replyString.indexOf("=") + valueLength); 457 log.debug("Current limit string: {}", tmpString); 458 try { 459 currentLimitFromHardware = Integer.parseInt(tmpString); 460 } 461 catch (NumberFormatException e) { 462 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorFrameDialogLimit"), 463 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 464 state = State.IDLE; 465 return; 466 } 467 // Value written is scaled from hardware units to mA 468 currentLimit = (int) (currentLimitFromHardware * sv.sprogType.getCurrentMultiplier()); 469 log.debug("Current limit scale factor: {}", sv.sprogType.getCurrentMultiplier()); 470 log.debug("Current limit from hardware: {} scaled to: {}mA", currentLimitFromHardware, currentLimit); 471 currentTextField.setText(String.valueOf(currentLimit)); 472 currentTextField.setEnabled(true); 473 474 // Next get the mode word 475 state = State.MODEQUERYSENT; 476 msg = new SprogMessage(1); 477 msg.setOpCode('M'); 478 nextLine("cmd: \"" + msg + "\"\n", ""); 479 tc.sendSprogMessage(msg, this); 480 startTimer(); 481 } 482 break; 483 case MODEQUERYSENT: 484 log.debug("reply in MODEQUERYSENT state: {}", replyString); 485 if (replyString.contains("M=")) { 486 stopTimer(); 487 tmpString = replyString.substring(replyString.indexOf("=") 488 + 2, replyString.indexOf("=") + 6); 489 // Value returned is in hex 490 try { 491 modeWord = Integer.parseInt(tmpString, 16); 492 } 493 catch (NumberFormatException e) { 494 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorFrameDialogWord"), 495 Bundle.getMessage("SprogConsoleTitle"), JmriJOptionPane.ERROR_MESSAGE); 496 state = State.IDLE; 497 return; 498 } 499 state = State.IDLE; 500 // Set Speed step radio buttons, etc., according to mode word 501 if ((modeWord & SprogConstants.STEP14_BIT) != 0) { 502 speed14Button.setSelected(true); 503 } else if ((modeWord & SprogConstants.STEP28_BIT) != 0) { 504 speed28Button.setSelected(true); 505 } else { 506 speed128Button.setSelected(true); 507 } 508 if ((modeWord & SprogConstants.ZTC_BIT) != 0) { 509 ztcCheckBox.setSelected(true); 510 } 511 if ((modeWord & SprogConstants.BLUE_BIT) != 0) { 512 blueCheckBox.setSelected(true); 513 } 514 } 515 break; 516 case CURRENTSENT: 517 // Any reply will do here 518 log.debug("reply in CURRENTSENT state: {}", replyString); 519 // Get new mode word - assume 128 steps 520 modeWord = SprogConstants.STEP128_BIT; 521 if (speed14Button.isSelected()) { 522 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP14_BIT; 523 } else if (speed28Button.isSelected()) { 524 modeWord = modeWord & ~SprogConstants.STEP_MASK | SprogConstants.STEP28_BIT; 525 } 526 527 // ZTC mode 528 if (ztcCheckBox.isSelected() == true) { 529 modeWord = modeWord | SprogConstants.ZTC_BIT; 530 } 531 532 // Blueline mode 533 if (blueCheckBox.isSelected() == true) { 534 modeWord = modeWord | SprogConstants.BLUE_BIT; 535 } 536 537 // firmware unlock 538 if (unlockCheckBox.isSelected() == true) { 539 modeWord = modeWord | SprogConstants.UNLOCK_BIT; 540 } 541 542 // Send new mode word 543 state = State.MODESENT; 544 msg = new SprogMessage("M " + modeWord); 545 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 546 tc.sendSprogMessage(msg, this); 547 break; 548 case MODESENT: 549 // Any reply will do here 550 log.debug("reply in MODESENT state: {}", replyString); 551 // Write to EEPROM 552 state = State.WRITESENT; 553 msg = new SprogMessage("W"); 554 nextLine("cmd: \"" + msg.toString(_memo.getSprogTrafficController().isSIIBootMode()) + "\"\n", ""); 555 tc.sendSprogMessage(msg, this); 556 break; 557 case WRITESENT: 558 // Any reply will do here 559 log.debug("reply in WRITESENT state: {}", replyString); 560 // All done 561 state = State.IDLE; 562 break; 563 default: 564 log.warn("Unhandled state: {}", state); 565 break; 566 } 567 } 568 569 /** 570 * Internal routine to handle a timeout. 571 */ 572 synchronized protected void timeout() { 573 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TypeTimeoutTalkingToSPROG"), 574 Bundle.getMessage("Timeout"), JmriJOptionPane.ERROR_MESSAGE); 575 state = State.IDLE; 576 } 577 578 protected int TIMEOUT = 1000; 579 580 javax.swing.Timer timer = null; 581 582 /** 583 * Internal routine to start timer to protect the mode-change. 584 */ 585 protected void startTimer() { 586 restartTimer(TIMEOUT); 587 } 588 589 /** 590 * Internal routine to stop timer, as all is well. 591 */ 592 protected void stopTimer() { 593 if (timer != null) { 594 timer.stop(); 595 } 596 } 597 598 /** 599 * Internal routine to handle timer starts and restarts. 600 * 601 * @param delay milliseconds to delay 602 */ 603 protected void restartTimer(int delay) { 604 if (timer == null) { 605 timer = new javax.swing.Timer(delay, (java.awt.event.ActionEvent e) -> { 606 timeout(); 607 }); 608 } 609 timer.stop(); 610 timer.setInitialDelay(delay); 611 timer.setRepeats(false); 612 timer.start(); 613 } 614 615 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogConsoleFrame.class); 616 617}