001package jmri.jmrix.bachrus; 002 003//<editor-fold defaultstate="collapsed" desc="Imports"> 004import jmri.jmrix.bachrus.speedmatcher.basic.BasicSpeedMatcherFactory; 005 006import java.awt.BorderLayout; 007import java.awt.CardLayout; 008import java.awt.Color; 009import java.awt.Dimension; 010import java.awt.FlowLayout; 011import java.awt.Font; 012import java.awt.GridBagConstraints; 013import java.awt.GridBagLayout; 014import java.awt.Insets; 015import java.beans.PropertyChangeEvent; 016import java.beans.PropertyChangeListener; 017import java.text.MessageFormat; 018import java.text.SimpleDateFormat; 019import java.util.*; 020 021import javax.swing.*; 022import javax.swing.border.*; 023 024import jmri.CommandStation; 025import jmri.DccLocoAddress; 026import jmri.DccThrottle; 027import jmri.GlobalProgrammerManager; 028import jmri.InstanceManager; 029import jmri.JmriException; 030import jmri.PowerManager; 031import jmri.ProgListener; 032import jmri.Programmer; 033import jmri.ProgrammerException; 034import jmri.SpeedStepMode; 035import jmri.ThrottleListener; 036import jmri.jmrit.DccLocoAddressSelector; 037import jmri.jmrit.roster.RosterEntry; 038import jmri.jmrit.roster.RosterEntrySelector; 039import jmri.jmrit.roster.swing.GlobalRosterEntryComboBox; 040import jmri.jmrix.bachrus.speedmatcher.*; 041import jmri.jmrix.bachrus.speedmatcher.SpeedMatcher.SpeedTableStep; 042import jmri.jmrix.bachrus.speedmatcher.basic.*; 043import jmri.jmrix.bachrus.speedmatcher.speedStepScale.*; 044import jmri.util.JmriJFrame; 045import jmri.util.swing.JmriJOptionPane; 046 047//</editor-fold> 048/** 049 * Frame for Speedo Console for Bachrus running stand reader interface 050 * 051 * @author Andrew Crosland Copyright (C) 2010 052 * @author Dennis Miller Copyright (C) 2015 053 * @author Todd Wegter Copyright (C) 2019-2024 054 */ 055public class SpeedoConsoleFrame extends JmriJFrame implements SpeedoListener, 056 ThrottleListener, 057 ProgListener, 058 PropertyChangeListener { 059 060 /** 061 * TODO: Complete the help file 062 */ 063 //<editor-fold defaultstate="collapsed" desc="Enums"> 064 protected enum DisplayType { 065 NUMERIC, DIAL 066 } 067 068 protected enum ProfileState { 069 IDLE, WAIT_FOR_THROTTLE, RUNNING 070 } 071 072 protected enum ProfileDirection { 073 FORWARD, REVERSE 074 } 075 076 protected enum ProgState { 077 IDLE, 078 READ1, 079 READ3, 080 READ4, 081 READ17, 082 READ18, 083 READ29, 084 WRITE3, 085 WRITE4, 086 } 087 //</editor-fold> 088 089 //<editor-fold defaultstate="collapsed" desc="Member Variables"> 090 //<editor-fold defaultstate="collapsed" desc="General GUI Elements"> 091 protected JLabel scaleLabel = new JLabel(); 092 protected JLabel customScaleLabel = new JLabel(); 093 protected JTextField customScaleField = new JTextField(3); 094 protected int customScale = 148; 095 protected JTextField speedTextField = new JTextField(12); 096 protected JPanel displayCards = new JPanel(); 097 098 protected ButtonGroup speedGroup = new ButtonGroup(); 099 protected JRadioButton mphButton = new JRadioButton(Bundle.getMessage("MPH")); 100 protected JRadioButton kphButton = new JRadioButton(Bundle.getMessage("KPH")); 101 protected ButtonGroup displayGroup = new ButtonGroup(); 102 protected JRadioButton numButton = new JRadioButton(Bundle.getMessage("Numeric")); 103 protected JRadioButton dialButton = new JRadioButton(Bundle.getMessage("Dial")); 104 protected SpeedoDial speedoDialDisplay = new SpeedoDial(); 105 protected JCheckBox dirFwdButton = new JCheckBox(Bundle.getMessage("ScanForward")); 106 protected JCheckBox dirRevButton = new JCheckBox(Bundle.getMessage("ScanReverse")); 107 protected JCheckBox toggleGridButton = new JCheckBox(Bundle.getMessage("ToggleGrid")); 108 109 protected JLabel statusLabel = new JLabel(" "); 110 protected javax.swing.JLabel readerLabel = new javax.swing.JLabel(); 111 //</editor-fold> 112 113 //<editor-fold defaultstate="collapsed" desc="General Member Variables"> 114 protected static final int DEFAULT_SCALE = 8; 115 116 protected float selectedScale = 0; 117 protected int series = 0; 118 protected float sampleSpeed = 0; 119 protected float targetSpeed = 0; 120 protected float currentSpeed = 0; 121 protected float incSpeed = 0; 122 protected float oldSpeed = 0; 123 protected float acc = 0; 124 protected float avSpeed = 0; 125 protected int range = 1; 126 protected float circ = 0; 127 protected float count = 1; 128 protected float freq; 129 protected static final int DISPLAY_UPDATE = 500; 130 protected static final int FAST_DISPLAY_RATIO = 5; 131 132 /* 133 * At low speed, readings arrive less often and less filtering 134 * is applied to minimize the delay in updating the display 135 * 136 * Speed measurement is split into 4 ranges with an overlap, to 137 * prevent "hunting" between the ranges. 138 */ 139 protected static final int RANGE1LO = 0; 140 protected static final int RANGE1HI = 9; 141 protected static final int RANGE2LO = 7; 142 protected static final int RANGE2HI = 31; 143 protected static final int RANGE3LO = 29; 144 protected static final int RANGE3HI = 62; 145 protected static final int RANGE4LO = 58; 146 protected static final int RANGE4HI = 9999; 147 static final int[] FILTER_LENGTH = {0, 3, 6, 10, 20}; 148 149 String selectedScalePref = this.getClass().getName() + ".SelectedScale"; // NOI18N 150 String customScalePref = this.getClass().getName() + ".CustomScale"; // NOI18N 151 String speedUnitsKphPref = this.getClass().getName() + ".SpeedUnitsKph"; // NOI18N 152 String dialTypePref = this.getClass().getName() + ".DialType"; // NOI18N 153 jmri.UserPreferencesManager prefs; 154 155 // members for handling the Speedo interface 156 SpeedoTrafficController tc = null; 157 158 protected String[] scaleStrings = new String[]{ 159 Bundle.getMessage("ScaleZ"), 160 Bundle.getMessage("ScaleEuroN"), 161 Bundle.getMessage("ScaleNFine"), 162 Bundle.getMessage("ScaleJapaneseN"), 163 Bundle.getMessage("ScaleBritishN"), 164 Bundle.getMessage("Scale3mm"), 165 Bundle.getMessage("ScaleTT"), 166 Bundle.getMessage("Scale00"), 167 Bundle.getMessage("ScaleH0"), 168 Bundle.getMessage("ScaleS"), 169 Bundle.getMessage("Scale048"), 170 Bundle.getMessage("Scale045"), 171 Bundle.getMessage("Scale043"), 172 Bundle.getMessage("ScaleOther") 173 }; 174 175 protected float[] scales = new float[]{ 176 220, 177 160, 178 152, 179 150, 180 148, 181 120, 182 101.6F, 183 76, 184 87, 185 64, 186 48, 187 45, 188 43, 189 -1 190 }; 191 192 //Create the combo box, and assign the scales to it 193 JComboBox<String> scaleList = new JComboBox<>(scaleStrings); 194 195 private SpeedoSystemConnectionMemo _memo = null; 196 197 protected DisplayType display = DisplayType.NUMERIC; 198 //</editor-fold> 199 200 //<editor-fold defaultstate="collapsed" desc="DCC Services"> 201 /* 202 * Keep track of the DCC services available 203 */ 204 protected int dccServices; 205 protected static final int BASIC = 0; 206 protected static final int PROG = 1; 207 protected static final int COMMAND = 2; 208 protected static final int THROTTLE = 4; 209 210 protected boolean timerRunning = false; 211 212 protected ProgState progState = ProgState.IDLE; 213 214 protected float throttleIncrement; 215 protected Programmer prog = null; 216 protected CommandStation commandStation = null; 217 218 private PowerManager pm = null; 219 //</editor-fold> 220 221 //<editor-fold defaultstate="collapsed" desc="Address Selector GUI Elements"> 222 //protected JLabel profileAddressLabel = new JLabel(Bundle.getMessage("LocoAddress")); 223 //protected JTextField profileAddressField = new JTextField(6); 224 protected JButton readAddressButton = new JButton(Bundle.getMessage("Read")); 225 226 private final DccLocoAddressSelector addrSelector = new DccLocoAddressSelector(); 227 private JButton setButton; 228 private GlobalRosterEntryComboBox rosterBox; 229 protected RosterEntry rosterEntry; 230 //</editor-fold> 231 232 //<editor-fold defaultstate="collapsed" desc="Address Selector Member Variables"> 233 private final boolean disableRosterBoxActions = false; 234 private DccLocoAddress locomotiveAddress = new DccLocoAddress(0, false); 235 236 //protected int profileAddress = 0; 237 protected int readAddress = 0; 238 //</editor-fold> 239 240 //<editor-fold defaultstate="collapsed" desc="Momentum GUI Elements"> 241 protected SpinnerNumberModel accelerationSM = new SpinnerNumberModel(0, 0, 255, 1); 242 protected SpinnerNumberModel decelerationSM = new SpinnerNumberModel(0, 0, 255, 1); 243 244 protected JLabel accelerationLabel = new JLabel(Bundle.getMessage("MomentumAccelLabel")); 245 protected JSpinner accelerationField = new JSpinner(accelerationSM); 246 247 protected JLabel decelerationLabel = new JLabel(Bundle.getMessage("MomentumDecelLabel")); 248 protected JSpinner decelerationField = new JSpinner(decelerationSM); 249 250 protected JButton readMomentumButton = new JButton(Bundle.getMessage("MomentumReadBtn")); 251 protected JButton setMomentumButton = new JButton(Bundle.getMessage("MomentumSetBtn")); 252 //</editor-fold> 253 254 //<editor-fold defaultstate="collapsed" desc="Speed Profile GUI Elements"> 255 protected JButton trackPowerButton = new JButton(Bundle.getMessage("PowerUp")); 256 protected JButton startProfileButton = new JButton(Bundle.getMessage("Start")); 257 protected JButton stopProfileButton = new JButton(Bundle.getMessage("Stop")); 258 protected JButton exportProfileButton = new JButton(Bundle.getMessage("Export")); 259 protected JButton printProfileButton = new JButton(Bundle.getMessage("Print")); 260 protected JButton resetGraphButton = new JButton(Bundle.getMessage("ResetGraph")); 261 protected JButton loadProfileButton = new JButton(Bundle.getMessage("LoadRef")); 262 protected JTextField printTitleText = new JTextField(); 263 264 GraphPane profileGraphPane; 265 //</editor-fold> 266 267 //<editor-fold defaultstate="collapsed" desc="Speed Profile Member Variables"> 268 protected DccSpeedProfile spFwd; 269 protected DccSpeedProfile spRev; 270 protected DccSpeedProfile spRef; 271 272 protected ProfileDirection profileDir = ProfileDirection.FORWARD; 273 protected DccThrottle throttle = null; 274 protected int profileStep = 0; 275 protected float profileSpeed; 276 277 protected ProfileState profileState = ProfileState.IDLE; 278 //</editor-fold> 279 280 //<editor-fold defaultstate="collapsed" desc="Speed Matching GUI Elements"> 281 //<editor-fold defaultstate="collapsed" desc="Basic"> 282 protected JLabel basicSpeedMatchInfo = new JLabel("<html><p>" 283 + Bundle.getMessage("BasicSpeedMatchDescLine1") 284 + "<br/><br/>" + Bundle.getMessage("BasicSpeedMatchDescLine2") 285 + "<br/><br/>" + Bundle.getMessage("BasicSpeedMatchDescSettings") 286 + "<br/><ul>" 287 + "<li>" + Bundle.getMessage("BasicSpeedMatchDescDigitrax") + "</li>" 288 + "<li>" + Bundle.getMessage("BasicSpeedMatchDescESU") + "</li>" 289 + "<li>" + Bundle.getMessage("BasicSpeedMatchDescNCE") + "</li>" 290 + "<li>" + Bundle.getMessage("BasicSpeedMatchDescSoundtraxx") + "</li>" 291 + "</ul>" 292 + Bundle.getMessage("BasicSpeedMatchDescLine3") 293 + "<br/><br/>" + Bundle.getMessage("BasicSpeedMatchDescLine4") 294 + "<br/><br/>" + Bundle.getMessage("BasicSpeedMatchDescLine5") 295 + "<br/><br/></p></html>"); 296 297 protected ButtonGroup basicSpeedMatcherTypeGroup = new ButtonGroup(); 298 protected JRadioButton basicSimpleCVSpeedMatchButton = new JRadioButton(Bundle.getMessage("SpeedMatchSimpleCVRadio")); 299 protected JRadioButton basicSpeedTableSpeedMatchButton = new JRadioButton(Bundle.getMessage("SpeedMatchSpeedTableRadio")); 300 protected JRadioButton basicESUSpeedMatchButton = new JRadioButton(Bundle.getMessage("SpeedMatchESUSpeedTableRadio")); 301 302 protected SpinnerNumberModel basicSpeedMatchWarmUpForwardSecondsSM = new SpinnerNumberModel(240, 0, 480, 1); 303 protected SpinnerNumberModel basicSpeedMatchWarmUpReverseSecondsSM = new SpinnerNumberModel(120, 0, 480, 1); 304 protected JCheckBox basicSpeedMatchReverseCheckbox = new JCheckBox(Bundle.getMessage("SpeedMatchTrimReverseChk")); 305 protected JCheckBox basicSpeedMatchWarmUpCheckBox = new JCheckBox(Bundle.getMessage("SpeedMatchWarmUpChk")); 306 protected JLabel basicSpeedMatchWarmUpForwardLabel = new JLabel(Bundle.getMessage("SpeedMatchForwardWarmUpLabel")); 307 protected JSpinner basicSpeedMatchWarmUpForwardSeconds = new JSpinner(basicSpeedMatchWarmUpForwardSecondsSM); 308 protected JLabel basicSpeedMatchWarmUpForwardUnit = new JLabel(Bundle.getMessage("SpeedMatchSecondsLabel")); 309 protected JLabel basicSpeedMatchWarmUpReverseLabel = new JLabel(Bundle.getMessage("SpeedMatchReverseWarmUpLabel")); 310 protected JSpinner basicSpeedMatchWarmUpReverseSeconds = new JSpinner(basicSpeedMatchWarmUpReverseSecondsSM); 311 protected JLabel basicSpeedMatchWarmUpReverseUnit = new JLabel(Bundle.getMessage("SpeedMatchSecondsLabel")); 312 313 protected JLabel basicSpeedMatchTargetStartSpeedLabel = new JLabel(Bundle.getMessage("BasioSpeedMatchStartSpeedLabel")); 314 protected SpinnerNumberModel startSpeedSM = new SpinnerNumberModel(3, 1, 255, 1); 315 protected JSpinner basicSpeedMatchTargetStartSpeedField = new JSpinner(startSpeedSM); 316 protected JLabel basicSpeedMatchTargetStartSpeedUnit = new JLabel(Bundle.getMessage("SpeedMatchMPHLabel")); 317 318 protected JLabel basicSpeedMatchTargetHighSpeedLabel = new JLabel(Bundle.getMessage("BasicSpeedMatchTopSpeedLabel")); 319 protected SpinnerNumberModel highSpeedSM = new SpinnerNumberModel(55, 1, 255, 1); 320 protected JSpinner basicSpeedMatchTargetHighSpeedField = new JSpinner(highSpeedSM); 321 protected JLabel basicSpeedMatchTargetHighSpeedUnit = new JLabel(Bundle.getMessage("SpeedMatchMPHLabel")); 322 protected JButton basicSpeedMatchStartStopButton = new JButton(Bundle.getMessage(("SpeedMatchStartBtn"))); 323 //</editor-fold> 324 325 //<editor-fold defaultstate="collapsed" desc="Advanced"> 326 protected JLabel speedStepScaleSpeedMatchInfo = new JLabel("<html><p>" 327 + Bundle.getMessage("AdvancedSpeedMatchDescLine1") 328 + "<br/><br/>" + Bundle.getMessage("AdvancedSpeedMatchDescLine2") 329 + "<br/><br/>" + Bundle.getMessage("AdvancedSpeedMatchDescSettings") 330 + "<br/><ul>" 331 + "<li>" + Bundle.getMessage("AdvancedSpeedMatchDescDigitrax") + "</li>" 332 + "<li>" + Bundle.getMessage("AdvancedSpeedMatchDescESU") + "</li>" 333 + "<li>" + Bundle.getMessage("AdvancedSpeedMatchDescNCE") + "</li>" 334 + "<li>" + Bundle.getMessage("AdvancedSpeedMatchDescSoundtraxx") + "</li>" 335 + "</ul>" 336 + Bundle.getMessage("AdvancedSpeedMatchDescLine3") 337 + "<br/><br/>" + Bundle.getMessage("AdvancedSpeedMatchDescLine4") 338 + "<br/><br/>" + Bundle.getMessage("AdvancedSpeedMatchDescLine5") 339 + "<br/><br/>" + Bundle.getMessage("AdvancedSpeedMatchDescLine6") 340 + "<br/><br/></p></html>"); 341 342 protected ButtonGroup speedStepScaleSpeedMatcherTypeGroup = new ButtonGroup(); 343 protected JRadioButton speedStepScaleSpeedTableSpeedMatchButton = new JRadioButton(Bundle.getMessage("SpeedMatchSpeedTableRadio")); 344 protected JRadioButton speedStepScaleESUSpeedMatchButton = new JRadioButton(Bundle.getMessage("SpeedMatchESUSpeedTableRadio")); 345 346 protected SpinnerNumberModel speedStepScaleSpeedMatchWarmUpForwardSecondsSM = new SpinnerNumberModel(240, 0, 480, 1); 347 protected SpinnerNumberModel speedStepScaleSpeedMatchWarmUpReverseSecondsSM = new SpinnerNumberModel(120, 0, 480, 1); 348 protected JCheckBox speedStepScaleSpeedMatchReverseCheckbox = new JCheckBox(Bundle.getMessage("SpeedMatchTrimReverseChk")); 349 protected JCheckBox speedStepScaleSpeedMatchWarmUpCheckBox = new JCheckBox(Bundle.getMessage("SpeedMatchWarmUpChk")); 350 protected JLabel speedStepScaleSpeedMatchWarmUpForwardLabel = new JLabel(Bundle.getMessage("SpeedMatchForwardWarmUpLabel")); 351 protected JSpinner speedStepScaleSpeedMatchWarmUpForwardSeconds = new JSpinner(speedStepScaleSpeedMatchWarmUpForwardSecondsSM); 352 protected JLabel speedStepScaleSpeedMatchWarmUpForwardUnit = new JLabel(Bundle.getMessage("SpeedMatchSecondsLabel")); 353 protected JLabel speedStepScaleSpeedMatchWarmUpReverseLabel = new JLabel(Bundle.getMessage("SpeedMatchReverseWarmUpLabel")); 354 protected JSpinner speedStepScaleSpeedMatchWarmUpReverseSeconds = new JSpinner(speedStepScaleSpeedMatchWarmUpReverseSecondsSM); 355 protected JLabel speedStepScaleSpeedMatchWarmUpReverseUnit = new JLabel(Bundle.getMessage("SpeedMatchSecondsLabel")); 356 357 protected JLabel speedStepScaleMaxSpeedTargetLabel = new JLabel(Bundle.getMessage("AdvancedSpeedMatchMaxSpeed")); 358 protected JComboBox<SpeedTableStepSpeed> speedStepScaleSpeedMatchMaxSpeedField = new JComboBox<>(); 359 protected JLabel speedStepScaleSpeedMatchMaxSpeedUnit = new JLabel(Bundle.getMessage("SpeedMatchMPHLabel")); 360 protected JButton speedStepScaleSpeedMatchStartStopButton = new JButton(Bundle.getMessage(("SpeedMatchStartBtn"))); 361 protected JLabel speedStepScaleMaxSpeedActualLabel = new JLabel(Bundle.getMessage("AdvancedSpeedMatchActualMaxSpeed"), SwingConstants.RIGHT); 362 protected JLabel speedStepScaleMaxSpeedActualField = new JLabel("___"); 363 protected JLabel speedStepScaleMaxSpeedActualUnit = new JLabel(Bundle.getMessage("SpeedMatchMPHLabel")); 364 //</editor-fold> 365 //</editor-fold> 366 367 //<editor-fold defaultstate="collapsed" desc="Speed Matching Member Variables"> 368 protected SpeedMatcher speedMatcher; 369 //</editor-fold> 370 //</editor-fold> 371 372 // For testing only, must be 1 for normal use 373 protected static final int SPEED_TEST_SCALE_FACTOR = 1; 374 375 /** 376 * Constructor for the SpeedoConsoleFrame 377 * 378 * @param memo the memo for the connection the Speedo is using 379 */ 380 public SpeedoConsoleFrame(SpeedoSystemConnectionMemo memo) { 381 super(); 382 _memo = memo; 383 } 384 385 /** 386 * Grabs the title for the SpeedoConsoleFrame 387 * 388 * @return the frame's title 389 */ 390 protected String title() { 391 return Bundle.getMessage("SpeedoConsole"); 392 } 393 394 /** 395 * Sets the description for the speed profile 396 */ 397 private void setTitle() { 398 Date today; 399 String result; 400 SimpleDateFormat formatter; 401 formatter = new SimpleDateFormat("EEE d MMM yyyy", Locale.getDefault()); 402 today = new Date(); 403 result = formatter.format(today); 404 String annotate = Bundle.getMessage("ProfileFor") + " " 405 + locomotiveAddress.getNumber() + " " + Bundle.getMessage("CreatedOn") 406 + " " + result; 407 printTitleText.setText(annotate); 408 } 409 410 /** 411 * Override for the JmriJFrame's dispose function 412 */ 413 @Override 414 public void dispose() { 415 if (prefs != null) { 416 prefs.setComboBoxLastSelection(selectedScalePref, (String) scaleList.getSelectedItem()); 417 prefs.setProperty(customScalePref, "customScale", customScale); 418 prefs.setSimplePreferenceState(speedUnitsKphPref, kphButton.isSelected()); 419 prefs.setSimplePreferenceState(dialTypePref, dialButton.isSelected()); 420 } 421 _memo.getTrafficController().removeSpeedoListener(this); 422 super.dispose(); 423 } 424 425 // FIXME: Why does the if statement in this method include a direct false? 426 /** 427 * Override for the JmriJFrame's initComponents function 428 */ 429 @Override 430 public void initComponents() { 431 prefs = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class); 432 433 setTitle(title()); 434 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 435 436 // What services do we have? 437 dccServices = BASIC; 438 if (InstanceManager.getNullableDefault(GlobalProgrammerManager.class) != null) { 439 if (InstanceManager.getDefault(GlobalProgrammerManager.class).isGlobalProgrammerAvailable()) { 440 prog = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer(); 441 dccServices |= PROG; 442 } 443 } 444 if (InstanceManager.getNullableDefault(jmri.ThrottleManager.class) != null) { 445 // otherwise we'll send speed commands 446 log.info("Using Throttle interface for profiling"); 447 dccServices |= THROTTLE; 448 } 449 450 if (InstanceManager.getNullableDefault(jmri.PowerManager.class) != null) { 451 pm = InstanceManager.getDefault(jmri.PowerManager.class); 452 pm.addPropertyChangeListener(this); 453 } 454 455 //<editor-fold defaultstate="collapsed" desc="GUI Layout and Button Handlers"> 456 //<editor-fold defaultstate="collapsed" desc="Basic Setup Panel"> 457 /* 458 * Setup pane for basic operations 459 */ 460 JPanel basicPane = new JPanel(); 461 basicPane.setLayout(new BoxLayout(basicPane, BoxLayout.Y_AXIS)); 462 463 // Scale panel to hold the scale selector 464 JPanel scalePanel = new JPanel(); 465 scalePanel.setBorder(BorderFactory.createTitledBorder( 466 BorderFactory.createEtchedBorder(), Bundle.getMessage("SelectScale"))); 467 scalePanel.setLayout(new FlowLayout()); 468 469 scaleList.setToolTipText(Bundle.getMessage("SelectScaleToolTip")); 470 String lastSelectedScale = prefs.getComboBoxLastSelection(selectedScalePref); 471 if (lastSelectedScale != null && !lastSelectedScale.equals("")) { 472 try { 473 scaleList.setSelectedItem(lastSelectedScale); 474 } catch (ArrayIndexOutOfBoundsException e) { 475 scaleList.setSelectedIndex(DEFAULT_SCALE); 476 } 477 } else { 478 scaleList.setSelectedIndex(DEFAULT_SCALE); 479 } 480 481 if (scaleList.getSelectedIndex() > -1) { 482 selectedScale = scales[scaleList.getSelectedIndex()]; 483 } 484 485 // Listen to selection of scale 486 scaleList.addActionListener(e -> { 487 selectedScale = scales[scaleList.getSelectedIndex()]; 488 checkCustomScale(); 489 }); 490 491 scaleLabel.setText(Bundle.getMessage("Scale")); 492 scaleLabel.setVisible(true); 493 494 readerLabel.setText(Bundle.getMessage("UnknownReader")); 495 readerLabel.setVisible(true); 496 497 scalePanel.add(scaleLabel); 498 scalePanel.add(scaleList); 499 scalePanel.add(readerLabel); 500 501 // Custom Scale panel to hold the custome scale selection 502 JPanel customScalePanel = new JPanel(); 503 customScalePanel.setBorder(BorderFactory.createTitledBorder( 504 BorderFactory.createEtchedBorder(), Bundle.getMessage("CustomScale"))); 505 customScalePanel.setLayout(new FlowLayout()); 506 507 customScaleLabel.setText("1: "); 508 customScaleLabel.setVisible(true); 509 customScaleField.setVisible(true); 510 try { 511 customScaleField.setText(prefs.getProperty(customScalePref, "customScale").toString()); 512 } catch (java.lang.NullPointerException npe) { 513 customScaleField.setText("1"); 514 } 515 checkCustomScale(); 516 getCustomScale(); 517 518 // Let user press return to enter custom scale 519 customScaleField.addActionListener(e -> getCustomScale()); 520 521 customScalePanel.add(customScaleLabel); 522 customScalePanel.add(customScaleField); 523 524 basicPane.add(scalePanel); 525 basicPane.add(customScalePanel); 526 //</editor-fold> 527 528 //<editor-fold defaultstate="collapsed" desc="Speedometer Panel"> 529 // Speed panel for the dial or digital speed display 530 JPanel speedPanel = new JPanel(); 531 speedPanel.setBorder(BorderFactory.createTitledBorder( 532 BorderFactory.createEtchedBorder(), Bundle.getMessage("MeasuredSpeed"))); 533 speedPanel.setLayout(new BoxLayout(speedPanel, BoxLayout.X_AXIS)); 534 535 // Display Panel which is a card layout with cards to show 536 // numeric or dial type speed display 537 displayCards.setLayout(new CardLayout()); 538 539 // Numeric speed card 540 JPanel numericSpeedPanel = new JPanel(); 541 numericSpeedPanel.setLayout(new BoxLayout(numericSpeedPanel, BoxLayout.X_AXIS)); 542 Font f = new Font("", Font.PLAIN, 96); 543 speedTextField.setFont(f); 544 speedTextField.setHorizontalAlignment(JTextField.RIGHT); 545 speedTextField.setColumns(3); 546 speedTextField.setText("0.0"); 547 speedTextField.setVisible(true); 548 speedTextField.setToolTipText(Bundle.getMessage("SpeedHere")); 549 numericSpeedPanel.add(speedTextField); 550 551 // Dial speed card 552 JPanel dialSpeedPanel = new JPanel(); 553 dialSpeedPanel.setLayout(new BoxLayout(dialSpeedPanel, BoxLayout.X_AXIS)); 554 dialSpeedPanel.add(speedoDialDisplay); 555 speedoDialDisplay.update(0.0F); 556 557 // Add cards to panel 558 displayCards.add(dialSpeedPanel, "DIAL"); 559 displayCards.add(numericSpeedPanel, "NUMERIC"); 560 CardLayout cl = (CardLayout) displayCards.getLayout(); 561 cl.show(displayCards, "DIAL"); 562 563 // button panel 564 JPanel buttonPanel = new JPanel(); 565 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); 566 speedGroup.add(mphButton); 567 speedGroup.add(kphButton); 568 mphButton.setToolTipText(Bundle.getMessage("TTDisplayMPH")); 569 kphButton.setToolTipText(Bundle.getMessage("TTDisplayKPH")); 570 mphButton.setSelected(!prefs.getSimplePreferenceState(speedUnitsKphPref)); 571 kphButton.setSelected(prefs.getSimplePreferenceState(speedUnitsKphPref)); 572 displayGroup.add(numButton); 573 displayGroup.add(dialButton); 574 numButton.setToolTipText(Bundle.getMessage("TTDisplayNumeric")); 575 dialButton.setToolTipText(Bundle.getMessage("TTDisplayDial")); 576 numButton.setSelected(!prefs.getSimplePreferenceState(dialTypePref)); 577 dialButton.setSelected(prefs.getSimplePreferenceState(dialTypePref)); 578 buttonPanel.add(mphButton); 579 buttonPanel.add(kphButton); 580 buttonPanel.add(numButton); 581 buttonPanel.add(dialButton); 582 583 speedPanel.add(displayCards); 584 speedPanel.add(buttonPanel); 585 586 // Listen to change of units, convert current average and update display 587 mphButton.addActionListener(e -> setUnits()); 588 kphButton.addActionListener(e -> setUnits()); 589 590 // Listen to change of display 591 numButton.addActionListener(e -> setDial()); 592 dialButton.addActionListener(e -> setDial()); 593 594 basicPane.add(speedPanel); 595 //</editor-fold> 596 597 //<editor-fold defaultstate="collapsed" desc="Address, Speed Profiling, Speed Matching, and Title Panel"> 598 JPanel profileAndSpeedMatchingPane = new JPanel(); 599 profileAndSpeedMatchingPane.setLayout(new BorderLayout()); 600 601 //<editor-fold defaultstate="collapsed" desc="Address and Momentum Panel"> 602 //<editor-fold defaultstate="collapsed" desc="Address Pane"> 603 JPanel addrPane = new JPanel(); 604 GridBagLayout gLayout = new GridBagLayout(); 605 GridBagConstraints gConstraints = new GridBagConstraints(); 606 gConstraints.insets = new Insets(3, 3, 3, 3); 607 Border addrPaneBorder = javax.swing.BorderFactory.createEtchedBorder(); 608 TitledBorder addrPaneTitle = javax.swing.BorderFactory.createTitledBorder(addrPaneBorder, Bundle.getMessage("LocoSelection")); 609 addrPane.setLayout(gLayout); 610 addrPane.setBorder(addrPaneTitle); 611 612 setButton = new JButton(Bundle.getMessage("ButtonSet")); 613 setButton.addActionListener(e -> changeOfAddress()); 614 addrSelector.setAddress(null); 615 616 rosterBox = new GlobalRosterEntryComboBox(); 617 rosterBox.setNonSelectedItem(Bundle.getMessage("NoLocoSelected")); 618 rosterBox.setToolTipText(Bundle.getMessage("TTSelectLocoFromRoster")); 619 620 /* 621 Using an ActionListener didn't select a loco from the ComboBox properly 622 so changed it to a PropertyChangeListener approach modeled on the code 623 in CombinedLocoSelPane class, layoutRosterSelection method, which is known to work. 624 Not sure why the ActionListener didn't work properly, but this fixes the bug 625 */ 626 rosterBox.addPropertyChangeListener(RosterEntrySelector.SELECTED_ROSTER_ENTRIES, pce -> { 627 if (!disableRosterBoxActions) { //Have roster box actions been disabled? 628 rosterItemSelected(); 629 } 630 }); 631 632 readAddressButton.setToolTipText(Bundle.getMessage("ReadLoco")); 633 634 addrPane.add(addrSelector.getCombinedJPanel(), gConstraints); 635 addrPane.add(new JLabel(" "), gConstraints); 636 addrPane.add(setButton, gConstraints); 637 addrPane.add(new JLabel(" "), gConstraints); 638 addrPane.add(rosterBox, gConstraints); 639 addrPane.add(new JLabel(" "), gConstraints); 640 addrPane.add(readAddressButton, gConstraints); 641 642 if ((dccServices & PROG) != PROG) { 643 // No programming facility so user must enter address 644 readAddressButton.setEnabled(false); 645 readMomentumButton.setEnabled(false); 646 } else { 647 readAddressButton.setEnabled(true); 648 readMomentumButton.setEnabled(true); 649 } 650 651 // Listen to read button 652 readAddressButton.addActionListener(e -> readAddress()); 653 //</editor-fold> 654 655 //<editor-fold defaultstate="collapsed" desc="Momentum Panel"> 656 JPanel momentumPane = new JPanel(); 657 momentumPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("MomentumTitle"))); 658 momentumPane.setLayout(new FlowLayout()); 659 momentumPane.add(accelerationLabel); 660 momentumPane.add(accelerationField); 661 momentumPane.add(decelerationLabel); 662 momentumPane.add(decelerationField); 663 momentumPane.add(readMomentumButton); 664 momentumPane.add(setMomentumButton); 665 666 // Listen to read momentum button 667 readMomentumButton.addActionListener(e -> readMomentum()); 668 669 //Listen to set momentum button 670 setMomentumButton.addActionListener(e -> setMomentum()); 671 //</editor-fold> 672 673 JPanel profileAndSpeedMatchingNorthPane = new JPanel(); 674 profileAndSpeedMatchingNorthPane.setLayout(new BoxLayout(profileAndSpeedMatchingNorthPane, BoxLayout.Y_AXIS)); 675 profileAndSpeedMatchingNorthPane.add(addrPane); 676 profileAndSpeedMatchingNorthPane.add(momentumPane); 677 678 profileAndSpeedMatchingPane.add(profileAndSpeedMatchingNorthPane, BorderLayout.NORTH); 679 //</editor-fold> 680 681 //<editor-fold defaultstate="collapsed" desc="Speed Matching and Profiling Panel"> 682 JTabbedPane profileAndSpeedMatchingTabs = new JTabbedPane(); 683 684 GridBagConstraints row1 = new GridBagConstraints(); 685 row1.anchor = GridBagConstraints.WEST; 686 row1.fill = GridBagConstraints.HORIZONTAL; 687 GridBagConstraints row2 = new GridBagConstraints(); 688 row2.gridy = 1; 689 row2.anchor = GridBagConstraints.EAST; 690 GridBagConstraints row3 = new GridBagConstraints(); 691 row3.gridy = 2; 692 row3.anchor = GridBagConstraints.WEST; 693 694 GridBagConstraints gbc = new GridBagConstraints(); 695 696 //<editor-fold defaultstate="collapsed" desc="Speed Profiling Tab"> 697 // Pane for profiling loco speed curve 698 JPanel profilePane = new JPanel(); 699 profilePane.setLayout(new BorderLayout()); 700 701 // pane to hold the graph 702 spFwd = new DccSpeedProfile(29); // 28 step plus step 0 703 spRev = new DccSpeedProfile(29); // 28 step plus step 0 704 spRef = new DccSpeedProfile(29); // 28 step plus step 0 705 profileGraphPane = new GraphPane(spFwd, spRev, spRef); 706 profileGraphPane.setPreferredSize(new Dimension(600, 300)); 707 profileGraphPane.setXLabel(Bundle.getMessage("SpeedStep")); 708 profileGraphPane.setUnitsMph(); 709 710 profilePane.add(profileGraphPane, BorderLayout.CENTER); 711 712 // pane to hold the buttons 713 JPanel profileButtonPane = new JPanel(); 714 profileButtonPane.setLayout(new FlowLayout()); 715 profileButtonPane.add(trackPowerButton); 716 trackPowerButton.setToolTipText(Bundle.getMessage("TTPower")); 717 profileButtonPane.add(startProfileButton); 718 startProfileButton.setToolTipText(Bundle.getMessage("TTStartProfile")); 719 profileButtonPane.add(stopProfileButton); 720 stopProfileButton.setToolTipText(Bundle.getMessage("TTStopProfile")); 721 profileButtonPane.add(exportProfileButton); 722 exportProfileButton.setToolTipText(Bundle.getMessage("TTSaveProfile")); 723 profileButtonPane.add(printProfileButton); 724 printProfileButton.setToolTipText(Bundle.getMessage("TTPrintProfile")); 725 profileButtonPane.add(resetGraphButton); 726 resetGraphButton.setToolTipText(Bundle.getMessage("TTResetGraph")); 727 profileButtonPane.add(loadProfileButton); 728 loadProfileButton.setToolTipText(Bundle.getMessage("TTLoadProfile")); 729 730 // pane to hold the title 731 JPanel titlePane = new JPanel(); 732 titlePane.setLayout(new BoxLayout(titlePane, BoxLayout.X_AXIS)); 733 titlePane.setBorder(new EmptyBorder(3, 0, 3, 0)); 734 //JTextArea profileTitle = new JTextArea("Title: "); 735 //profileTitlePane.add(profileTitle); 736 printTitleText.setToolTipText(Bundle.getMessage("TTPrintTitle")); 737 printTitleText.setText(Bundle.getMessage("TTText1")); 738 titlePane.add(printTitleText); 739 740 // pane to wrap buttons and title 741 JPanel profileSouthPane = new JPanel(); 742 profileSouthPane.setLayout(new BoxLayout(profileSouthPane, BoxLayout.Y_AXIS)); 743 profileSouthPane.add(profileButtonPane); 744 profileSouthPane.add(titlePane); 745 746 profilePane.add(profileSouthPane, BorderLayout.SOUTH); 747 748 // Pane to hold controls 749 JPanel profileControlPane = new JPanel(); 750 profileControlPane.setLayout(new BoxLayout(profileControlPane, BoxLayout.Y_AXIS)); 751 dirFwdButton.setSelected(true); 752 dirFwdButton.setToolTipText(Bundle.getMessage("TTMeasFwd")); 753 dirRevButton.setToolTipText(Bundle.getMessage("TTMeasRev")); 754 dirFwdButton.setForeground(Color.RED); 755 dirRevButton.setForeground(Color.BLUE); 756 profileControlPane.add(dirFwdButton); 757 profileControlPane.add(dirRevButton); 758 toggleGridButton.setSelected(true); 759 profileControlPane.add(toggleGridButton); 760 profileGraphPane.showGrid(toggleGridButton.isSelected()); 761 762 profilePane.add(profileControlPane, BorderLayout.EAST); 763 764 profileAndSpeedMatchingTabs.addTab("Speed Profile", profilePane); 765 766 //<editor-fold defaultstate="collapsed" desc="Speed Profiling Button Handlers"> 767 // Listen to track Power button 768 trackPowerButton.addActionListener(e -> trackPower()); 769 770 // Listen to start profile button 771 startProfileButton.addActionListener(e -> { 772 getCustomScale(); 773 startProfile(); 774 }); 775 776 // Listen to stop profile button 777 stopProfileButton.addActionListener(e -> stopProfileAndSpeedMatch()); 778 779 // Listen to grid button 780 toggleGridButton.addActionListener(e -> { 781 profileGraphPane.showGrid(toggleGridButton.isSelected()); 782 profileGraphPane.repaint(); 783 }); 784 785 // Listen to export button 786 exportProfileButton.addActionListener(e -> { 787 if (dirFwdButton.isSelected() && dirRevButton.isSelected()) { 788 DccSpeedProfile[] sp = {spFwd, spRev}; 789 DccSpeedProfile.export(sp, locomotiveAddress.getNumber(), profileGraphPane.getUnits()); 790 } else if (dirFwdButton.isSelected()) { 791 DccSpeedProfile.export(spFwd, locomotiveAddress.getNumber(), "fwd", profileGraphPane.getUnits()); 792 } else if (dirRevButton.isSelected()) { 793 DccSpeedProfile.export(spRev, locomotiveAddress.getNumber(), "rev", profileGraphPane.getUnits()); 794 } 795 }); 796 797 // Listen to print button 798 printProfileButton.addActionListener(e -> profileGraphPane.printProfile(printTitleText.getText())); 799 800 // Listen to reset graph button 801 resetGraphButton.addActionListener(e -> { 802 spFwd.clear(); 803 spRev.clear(); 804 spRef.clear(); 805 speedoDialDisplay.reset(); 806 profileGraphPane.repaint(); 807 }); 808 809 // Listen to Load Reference button 810 loadProfileButton.addActionListener(e -> { 811 spRef.clear(); 812 int response = spRef.importDccProfile(profileGraphPane.getUnits()); 813 if (response == -1) { 814 statusLabel.setText(Bundle.getMessage("StatFileError")); 815 } else { 816 statusLabel.setText(Bundle.getMessage("StatFileSuccess")); 817 } 818 profileGraphPane.repaint(); 819 }); 820 //</editor-fold> 821 //</editor-fold> 822 823 //<editor-fold defaultstate="collapsed" desc="Basic Speed Matching Tab"> 824 basicSpeedMatcherTypeGroup.add(basicSimpleCVSpeedMatchButton); 825 basicSpeedMatcherTypeGroup.add(basicSpeedTableSpeedMatchButton); 826 basicSpeedMatcherTypeGroup.add(basicESUSpeedMatchButton); 827 basicSimpleCVSpeedMatchButton.setSelected(true); 828 829 basicSpeedMatchReverseCheckbox.setSelected(true); 830 basicSpeedMatchWarmUpCheckBox.setSelected(true); 831 832 JPanel basicSpeedMatcherPane = new JPanel(); 833 basicSpeedMatcherPane.setLayout(new BorderLayout()); 834 JPanel basicSpeedMatchSettingsPane = new JPanel(); 835 basicSpeedMatchSettingsPane.setLayout(new BoxLayout(basicSpeedMatchSettingsPane, BoxLayout.PAGE_AXIS)); 836 837 //Important Information 838 JPanel basicSpeedMatchImportantInfoPane = new JPanel(); 839 basicSpeedMatchImportantInfoPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchDescTitle"))); 840 basicSpeedMatchImportantInfoPane.setLayout(new BoxLayout(basicSpeedMatchImportantInfoPane, BoxLayout.LINE_AXIS)); 841 basicSpeedMatchImportantInfoPane.add(basicSpeedMatchInfo); 842 basicSpeedMatchSettingsPane.add(basicSpeedMatchImportantInfoPane); 843 844 //Speed Matcher Mode 845 JPanel basicSpeedMatchModePane = new JPanel(); 846 basicSpeedMatchModePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchModeTitle"))); 847 basicSpeedMatchModePane.setLayout(new FlowLayout()); 848 basicSpeedMatchModePane.add(basicSimpleCVSpeedMatchButton); 849 basicSpeedMatchModePane.add(basicSpeedTableSpeedMatchButton); 850 basicSpeedMatchModePane.add(basicESUSpeedMatchButton); 851 basicSpeedMatchSettingsPane.add(basicSpeedMatchModePane); 852 853 //Other Settings 854 JPanel basicSpeedMatchOtherSettingsPane = new JPanel(); 855 basicSpeedMatchOtherSettingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchOtherSettingTitle"))); 856 basicSpeedMatchOtherSettingsPane.setLayout(new GridBagLayout()); 857 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpCheckBox, row1); 858 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpForwardLabel, row2); 859 basicSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 860 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpForwardSeconds, row2); 861 basicSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 862 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpForwardUnit, row2); 863 basicSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(30, 0)), row2); 864 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpReverseLabel, row2); 865 basicSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 866 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpReverseSeconds, row2); 867 basicSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 868 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchWarmUpReverseUnit, row2); 869 basicSpeedMatchOtherSettingsPane.add(basicSpeedMatchReverseCheckbox, row3); 870 basicSpeedMatchSettingsPane.add(basicSpeedMatchOtherSettingsPane); 871 872 //Speed Settings 873 JPanel basicSpeedMatchSpeedPane = new JPanel(); 874 basicSpeedMatchSpeedPane.setLayout(new GridBagLayout()); 875 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetStartSpeedLabel, gbc); 876 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 877 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetStartSpeedField, gbc); 878 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 879 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetStartSpeedUnit, gbc); 880 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(15, 0)), gbc); 881 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetHighSpeedLabel, gbc); 882 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 883 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetHighSpeedField, gbc); 884 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 885 basicSpeedMatchSpeedPane.add(basicSpeedMatchTargetHighSpeedUnit, gbc); 886 basicSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(15, 0)), gbc); 887 basicSpeedMatchSpeedPane.add(basicSpeedMatchStartStopButton, gbc); 888 889 basicSpeedMatcherPane.add(basicSpeedMatchSettingsPane, BorderLayout.NORTH); 890 basicSpeedMatcherPane.add(basicSpeedMatchSpeedPane, BorderLayout.CENTER); 891 892 profileAndSpeedMatchingTabs.add(Bundle.getMessage("BasicSpeedMatchTab"), basicSpeedMatcherPane); 893 894 //<editor-fold defaultstate="collapsed" desc="Basic Speed Matcher Button Handlers"> 895 // Listen to speed match button 896 basicSpeedMatchStartStopButton.addActionListener(e -> { 897 int targetStartSpeed; 898 int targetHighSpeed; 899 boolean speedMatchReverse; 900 boolean warmUpLoco; 901 int warmUpForwardSeconds; 902 int warmUpReverseSeconds; 903 904 BasicSpeedMatcherConfig.SpeedTable speedTableType; 905 906 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 907 targetStartSpeed = startSpeedSM.getNumber().intValue(); 908 targetHighSpeed = highSpeedSM.getNumber().intValue(); 909 910 if (basicSpeedTableSpeedMatchButton.isSelected()) { 911 speedTableType = BasicSpeedMatcherConfig.SpeedTable.ADVANCED; 912 } else if (basicESUSpeedMatchButton.isSelected()) { 913 speedTableType = BasicSpeedMatcherConfig.SpeedTable.ESU; 914 } else { 915 speedTableType = BasicSpeedMatcherConfig.SpeedTable.SIMPLE; 916 } 917 918 speedMatchReverse = basicSpeedMatchReverseCheckbox.isSelected(); 919 warmUpLoco = basicSpeedMatchWarmUpCheckBox.isSelected(); 920 warmUpForwardSeconds = basicSpeedMatchWarmUpForwardSecondsSM.getNumber().intValue(); 921 warmUpReverseSeconds = basicSpeedMatchWarmUpReverseSecondsSM.getNumber().intValue(); 922 923 speedMatcher = BasicSpeedMatcherFactory.getSpeedMatcher( 924 speedTableType, 925 new BasicSpeedMatcherConfig( 926 locomotiveAddress, 927 targetStartSpeed, 928 targetHighSpeed, 929 mphButton.isSelected() ? Speed.Unit.MPH : Speed.Unit.KPH, 930 speedMatchReverse, 931 warmUpLoco ? warmUpForwardSeconds : 0, 932 warmUpLoco ? warmUpReverseSeconds : 0, 933 pm, 934 statusLabel, 935 basicSpeedMatchStartStopButton 936 ) 937 ); 938 939 if (!speedMatcher.startSpeedMatcher()) { 940 speedMatcher = null; 941 } 942 } else { 943 stopProfileAndSpeedMatch(); 944 } 945 }); 946 947 basicSpeedMatchWarmUpCheckBox.addActionListener(e -> { 948 boolean enableWarmUp = basicSpeedMatchWarmUpCheckBox.isSelected(); 949 950 basicSpeedMatchWarmUpForwardLabel.setEnabled(enableWarmUp); 951 basicSpeedMatchWarmUpForwardSeconds.setEnabled(enableWarmUp); 952 basicSpeedMatchWarmUpForwardUnit.setEnabled(enableWarmUp); 953 basicSpeedMatchWarmUpReverseLabel.setEnabled(enableWarmUp); 954 basicSpeedMatchWarmUpReverseSeconds.setEnabled(enableWarmUp); 955 basicSpeedMatchWarmUpReverseUnit.setEnabled(enableWarmUp); 956 }); 957 //</editor-fold> 958 //</editor-fold> 959 960 //<editor-fold defaultstate="collapsed" desc="Advanced Speed Matcher Tab"> 961 speedStepScaleSpeedMatcherTypeGroup.add(speedStepScaleSpeedTableSpeedMatchButton); 962 speedStepScaleSpeedMatcherTypeGroup.add(speedStepScaleESUSpeedMatchButton); 963 speedStepScaleSpeedTableSpeedMatchButton.setSelected(true); 964 965 speedStepScaleSpeedMatchReverseCheckbox.setSelected(true); 966 speedStepScaleSpeedMatchWarmUpCheckBox.setSelected(true); 967 968 JPanel speedStepScaleSpeedMatcherPane = new JPanel(); 969 speedStepScaleSpeedMatcherPane.setLayout(new BorderLayout()); 970 JPanel speedStepScaleSpeedMatchSettingsPane = new JPanel(); 971 speedStepScaleSpeedMatchSettingsPane.setLayout(new BoxLayout(speedStepScaleSpeedMatchSettingsPane, BoxLayout.PAGE_AXIS)); 972 973 //Important Information 974 JPanel speedStepScaleSpeedMatchImportantInfoPane = new JPanel(); 975 speedStepScaleSpeedMatchImportantInfoPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchDescTitle"))); 976 speedStepScaleSpeedMatchImportantInfoPane.setLayout(new BoxLayout(speedStepScaleSpeedMatchImportantInfoPane, BoxLayout.LINE_AXIS)); 977 speedStepScaleSpeedMatchImportantInfoPane.add(speedStepScaleSpeedMatchInfo); 978 speedStepScaleSpeedMatchSettingsPane.add(speedStepScaleSpeedMatchImportantInfoPane); 979 980 //Speed Matcher Mode 981 JPanel speedStepScaleSpeedMatchModePane = new JPanel(); 982 speedStepScaleSpeedMatchModePane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchModeTitle"))); 983 speedStepScaleSpeedMatchModePane.setLayout(new FlowLayout()); 984 speedStepScaleSpeedMatchModePane.add(speedStepScaleSpeedTableSpeedMatchButton); 985 speedStepScaleSpeedMatchModePane.add(speedStepScaleESUSpeedMatchButton); 986 speedStepScaleSpeedMatchSettingsPane.add(speedStepScaleSpeedMatchModePane); 987 988 //Other Settings 989 JPanel speedStepScaleSpeedMatchOtherSettingsPane = new JPanel(); 990 speedStepScaleSpeedMatchOtherSettingsPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Bundle.getMessage("SpeedMatchOtherSettingTitle"))); 991 speedStepScaleSpeedMatchOtherSettingsPane.setLayout(new GridBagLayout()); 992 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpCheckBox, row1); 993 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpForwardLabel, row2); 994 speedStepScaleSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 995 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpForwardSeconds, row2); 996 speedStepScaleSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 997 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpForwardUnit, row2); 998 speedStepScaleSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(30, 0)), row2); 999 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpReverseLabel, row2); 1000 speedStepScaleSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 1001 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpReverseSeconds, row2); 1002 speedStepScaleSpeedMatchOtherSettingsPane.add(Box.createRigidArea(new Dimension(5, 0)), row2); 1003 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchWarmUpReverseUnit, row2); 1004 speedStepScaleSpeedMatchOtherSettingsPane.add(speedStepScaleSpeedMatchReverseCheckbox, row3); 1005 speedStepScaleSpeedMatchSettingsPane.add(speedStepScaleSpeedMatchOtherSettingsPane); 1006 1007 //Speed Settings 1008 SpeedTableStep tempStep = SpeedTableStep.STEP1; 1009 while (tempStep != null) { 1010 speedStepScaleSpeedMatchMaxSpeedField.addItem(new SpeedTableStepSpeed(tempStep)); 1011 tempStep = tempStep.getNext(); 1012 } 1013 speedStepScaleSpeedMatchMaxSpeedField.setSelectedIndex(12); 1014 1015 JPanel speedStepScaleSpeedMatchSpeedPane = new JPanel(); 1016 speedStepScaleSpeedMatchSpeedPane.setLayout(new GridBagLayout()); 1017 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleMaxSpeedTargetLabel, gbc); 1018 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 1019 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleSpeedMatchMaxSpeedField, gbc); 1020 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 1021 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleSpeedMatchMaxSpeedUnit, gbc); 1022 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(15, 0)), gbc); 1023 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleSpeedMatchStartStopButton, gbc); 1024 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(15, 0)), gbc); 1025 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleMaxSpeedActualLabel, gbc); 1026 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 1027 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleMaxSpeedActualField, gbc); 1028 speedStepScaleSpeedMatchSpeedPane.add(Box.createRigidArea(new Dimension(5, 0)), gbc); 1029 speedStepScaleSpeedMatchSpeedPane.add(speedStepScaleMaxSpeedActualUnit, gbc); 1030 1031 speedStepScaleSpeedMatcherPane.add(speedStepScaleSpeedMatchSettingsPane, BorderLayout.NORTH); 1032 speedStepScaleSpeedMatcherPane.add(speedStepScaleSpeedMatchSpeedPane, BorderLayout.CENTER); 1033 1034 profileAndSpeedMatchingTabs.add(Bundle.getMessage("AdvancedSpeedMatchTab"), speedStepScaleSpeedMatcherPane); 1035 1036 //<editor-fold defaultstate="collapsed" desc="Speed Step Scale Speed Matcher Button Handlers"> 1037 // Listen to speed match button 1038 speedStepScaleSpeedMatchStartStopButton.addActionListener(e -> { 1039 SpeedTableStepSpeed targetMaxSpeedStep; 1040 boolean speedMatchReverse; 1041 boolean warmUpLoco; 1042 int warmUpForwardSeconds; 1043 int warmUpReverseSeconds; 1044 1045 SpeedStepScaleSpeedMatcherConfig.SpeedTable speedTableType; 1046 1047 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1048 targetMaxSpeedStep = (SpeedTableStepSpeed)speedStepScaleSpeedMatchMaxSpeedField.getSelectedItem(); 1049 1050 if (speedStepScaleESUSpeedMatchButton.isSelected()) { 1051 speedTableType = SpeedStepScaleSpeedMatcherConfig.SpeedTable.ESU; 1052 } else { 1053 speedTableType = SpeedStepScaleSpeedMatcherConfig.SpeedTable.ADVANCED; 1054 } 1055 1056 speedMatchReverse = speedStepScaleSpeedMatchReverseCheckbox.isSelected(); 1057 warmUpLoco = speedStepScaleSpeedMatchWarmUpCheckBox.isSelected(); 1058 warmUpForwardSeconds = speedStepScaleSpeedMatchWarmUpForwardSecondsSM.getNumber().intValue(); 1059 warmUpReverseSeconds = speedStepScaleSpeedMatchWarmUpReverseSecondsSM.getNumber().intValue(); 1060 1061 speedMatcher = SpeedStepScaleSpeedMatcherFactory.getSpeedMatcher( 1062 speedTableType, 1063 new SpeedStepScaleSpeedMatcherConfig( 1064 locomotiveAddress, 1065 targetMaxSpeedStep, 1066 mphButton.isSelected() ? Speed.Unit.MPH : Speed.Unit.KPH, 1067 speedMatchReverse, 1068 warmUpLoco ? warmUpForwardSeconds : 0, 1069 warmUpLoco ? warmUpReverseSeconds : 0, 1070 pm, 1071 statusLabel, 1072 speedStepScaleMaxSpeedActualField, 1073 speedStepScaleSpeedMatchStartStopButton 1074 ) 1075 ); 1076 1077 if (!speedMatcher.startSpeedMatcher()) { 1078 speedMatcher = null; 1079 } 1080 } else { 1081 stopProfileAndSpeedMatch(); 1082 } 1083 }); 1084 1085 speedStepScaleSpeedMatchWarmUpCheckBox.addActionListener(e -> { 1086 boolean enableWarmUp = speedStepScaleSpeedMatchWarmUpCheckBox.isSelected(); 1087 1088 speedStepScaleSpeedMatchWarmUpForwardLabel.setEnabled(enableWarmUp); 1089 speedStepScaleSpeedMatchWarmUpForwardSeconds.setEnabled(enableWarmUp); 1090 speedStepScaleSpeedMatchWarmUpForwardUnit.setEnabled(enableWarmUp); 1091 speedStepScaleSpeedMatchWarmUpReverseLabel.setEnabled(enableWarmUp); 1092 speedStepScaleSpeedMatchWarmUpReverseSeconds.setEnabled(enableWarmUp); 1093 speedStepScaleSpeedMatchWarmUpReverseUnit.setEnabled(enableWarmUp); 1094 }); 1095 //</editor-fold> 1096 //</editor-fold> 1097 1098 profileAndSpeedMatchingPane.add(profileAndSpeedMatchingTabs, BorderLayout.CENTER); 1099 //</editor-fold> 1100 //</editor-fold> 1101 //</editor-fold> 1102 1103 // Create the main pane and add the sub-panes 1104 JPanel mainPane = new JPanel(); 1105 mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.X_AXIS)); 1106 // make basic panel 1107 mainPane.add(basicPane); 1108 1109 if (((dccServices & THROTTLE) == THROTTLE) || ((dccServices & COMMAND) == COMMAND)) { 1110 mainPane.add(profileAndSpeedMatchingPane); 1111 } else { 1112 log.info("{} Connection:{}", Bundle.getMessage("StatNoDCC"), _memo.getUserName()); 1113 statusLabel.setText(Bundle.getMessage("StatNoDCC")); 1114 } 1115 1116 // add help menu to window 1117 addHelpMenu("package.jmri.jmrix.bachrus.SpeedoConsoleFrame", true); 1118 1119 // Create a wrapper with a status line and add the main content 1120 JPanel statusWrapper = new JPanel(); 1121 statusWrapper.setLayout(new BorderLayout()); 1122 JPanel statusPanel = new JPanel(); 1123 statusPanel.setLayout(new BorderLayout()); 1124 statusPanel.add(statusLabel, BorderLayout.WEST); 1125 1126 statusPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 1127 statusWrapper.add(mainPane, BorderLayout.CENTER); 1128 statusWrapper.add(statusPanel, BorderLayout.SOUTH); 1129 1130 getContentPane().add(statusWrapper); 1131 //</editor-fold> 1132 1133 // connect to TrafficController 1134 tc = _memo.getTrafficController(); 1135 tc.addSpeedoListener(this); 1136 1137 setUnits(); 1138 setDial(); 1139 1140 // pack for display 1141 pack(); 1142 1143 speedoDialDisplay.scaleFace(); 1144 } 1145 1146 //<editor-fold defaultstate="collapsed" desc="Speed Reader and Calculations"> 1147 /** 1148 * Handle "replies" from the hardware. In fact, all the hardware does is 1149 * send a constant stream of unsolicited speed updates. 1150 * 1151 * @param l the reply to handle 1152 */ 1153 @Override 1154 public synchronized void reply(SpeedoReply l) { // receive a reply message and log it 1155 //log.debug("Speedo reply " + l.toString()); 1156 count = l.getCount(); 1157 series = l.getSeries(); 1158 if (count > 0) { 1159 switch (series) { 1160 case 4: 1161 circ = 12.5664F; 1162 readerLabel.setText(Bundle.getMessage("Reader40")); 1163 break; 1164 case 5: 1165 circ = 18.8496F; 1166 readerLabel.setText(Bundle.getMessage("Reader50")); 1167 break; 1168 case 6: 1169 circ = 50.2655F; 1170 readerLabel.setText(Bundle.getMessage("Reader60")); 1171 break; 1172 case 103: 1173 circ = (float) ((5.95 + 0.9) * Math.PI); 1174 readerLabel.setText(Bundle.getMessage("Reader103")); 1175 break; 1176 default: 1177 speedTextField.setText(Bundle.getMessage("ReaderErr")); 1178 log.error("Invalid reader type"); 1179 break; 1180 } 1181 1182 // Update speed 1183 calcSpeed(); 1184 } 1185 if (timerRunning == false) { 1186 // first reply starts the timer 1187 startReplyTimer(); 1188 startDisplayTimer(); 1189 startFastDisplayTimer(); 1190 timerRunning = true; 1191 } else { 1192 // subsequent replies restart it 1193 replyTimer.restart(); 1194 } 1195 } 1196 1197 /** 1198 * Calculates the scale speed in KPH 1199 */ 1200 protected void calcSpeed() { 1201 float thisScale = (selectedScale == -1) ? customScale : selectedScale; 1202 if (series == 103) { 1203 // KPF-Zeller 1204 // calculate kph: r/sec * circumference converted to hours and kph in scaleFace() 1205 sampleSpeed = (float) ((count / 8.) * circ * 3600 / 1.0E6 * thisScale * SPEED_TEST_SCALE_FACTOR); 1206 // data arrives at constant rate, so we don't average nor switch range 1207 avSpeed = sampleSpeed; 1208 log.debug("New KPF-Zeller sample: {} Average: {}", sampleSpeed, avSpeed); 1209 1210 } else if (series > 0 && series <= 6) { 1211 // Bachrus 1212 // Scale the data and calculate kph 1213 try { 1214 freq = 1500000 / count; 1215 sampleSpeed = (freq / 24) * circ * thisScale * 3600 / 1000000 * SPEED_TEST_SCALE_FACTOR; 1216 } catch (ArithmeticException ae) { 1217 log.error("Exception calculating sampleSpeed", ae); 1218 } 1219 avFn(sampleSpeed); 1220 log.debug("New Bachrus sample: {} Average: {}", sampleSpeed, avSpeed); 1221 log.debug("Acc: {} range: {}", acc, range); 1222 switchRange(); 1223 } 1224 } 1225 1226 /** 1227 * Calculates the average speed using a filter 1228 * 1229 * @param speed the speed of the latest interation 1230 */ 1231 protected void avFn(float speed) { 1232 // Averaging function used for speed is 1233 // S(t) = S(t-1) - [S(t-1)/N] + speed 1234 // A(t) = S(t)/N 1235 // 1236 // where S is an accumulator, N is the length of the filter (i.e., 1237 // the number of samples included in the rolling average), and A is 1238 // the result of the averaging function. 1239 // 1240 // Re-arranged 1241 // S(t) = S(t-1) - A(t-1) + speed 1242 // A(t) = S(t)/N 1243 acc = acc - avSpeed + speed; 1244 avSpeed = acc / FILTER_LENGTH[range]; 1245 } 1246 1247 /** 1248 * Clears the average speed calculation 1249 */ 1250 protected void avClr() { 1251 acc = 0; 1252 avSpeed = 0; 1253 } 1254 1255 /** 1256 * Switches the filter used for averaging speed based on the measured speed 1257 */ 1258 protected void switchRange() { 1259 // When we switch range we must compensate the current accumulator 1260 // value for the longer filter. 1261 switch (range) { 1262 case 1: 1263 if (sampleSpeed > RANGE1HI) { 1264 range++; 1265 acc = acc * FILTER_LENGTH[2] / FILTER_LENGTH[1]; 1266 } 1267 break; 1268 case 2: 1269 if (sampleSpeed < RANGE2LO) { 1270 range--; 1271 acc = acc * FILTER_LENGTH[1] / FILTER_LENGTH[2]; 1272 } else if (sampleSpeed > RANGE2HI) { 1273 range++; 1274 acc = acc * FILTER_LENGTH[3] / FILTER_LENGTH[2]; 1275 } 1276 break; 1277 case 3: 1278 if (sampleSpeed < RANGE3LO) { 1279 range--; 1280 acc = acc * FILTER_LENGTH[2] / FILTER_LENGTH[3]; 1281 } else if (sampleSpeed > RANGE3HI) { 1282 range++; 1283 acc = acc * FILTER_LENGTH[4] / FILTER_LENGTH[3]; 1284 } 1285 break; 1286 case 4: 1287 if (sampleSpeed < RANGE4LO) { 1288 range--; 1289 acc = acc * FILTER_LENGTH[3] / FILTER_LENGTH[4]; 1290 } 1291 break; 1292 default: 1293 log.debug("range {} unsupported, range unchanged.", range); 1294 } 1295 } 1296 1297 /** 1298 * Displays the speed in the SpeedoConsoleFrame's digital/analog speedometer 1299 */ 1300 protected void showSpeed() { 1301 float speedForText = currentSpeed; 1302 if (mphButton.isSelected()) { 1303 speedForText = Speed.kphToMph(speedForText); 1304 } 1305 if (series > 0) { 1306 if ((currentSpeed < 0) || (currentSpeed > 999)) { 1307 log.error("Calculated speed out of range: {}", currentSpeed); 1308 speedTextField.setText("999"); 1309 } else { 1310 // Final smoothing as applied by Bachrus Console. Don't update display 1311 // unless speed has changed more than 2% 1312 if ((currentSpeed > oldSpeed * 1.02) || (currentSpeed < oldSpeed * 0.98)) { 1313 speedTextField.setText(MessageFormat.format("{0,number,##0.0}", speedForText)); 1314 speedTextField.setHorizontalAlignment(JTextField.RIGHT); 1315 oldSpeed = currentSpeed; 1316 speedoDialDisplay.update(currentSpeed); 1317 } 1318 } 1319 } 1320 } 1321 //</editor-fold> 1322 1323 //<editor-fold defaultstate="collapsed" desc="Speedometer Helper Functions"> 1324 /** 1325 * Check if custom scale selected and enable the custom scale entry field. 1326 */ 1327 protected void checkCustomScale() { 1328 if (selectedScale == -1) { 1329 customScaleField.setEnabled(true); 1330 } else { 1331 customScaleField.setEnabled(false); 1332 } 1333 } 1334 1335 /** 1336 * Set the speed to be displayed as a dial or numeric 1337 */ 1338 protected void setDial() { 1339 CardLayout cl = (CardLayout) displayCards.getLayout(); 1340 if (numButton.isSelected()) { 1341 display = DisplayType.NUMERIC; 1342 cl.show(displayCards, "NUMERIC"); 1343 } else { 1344 display = DisplayType.DIAL; 1345 cl.show(displayCards, "DIAL"); 1346 } 1347 } 1348 1349 /** 1350 * Set the displays to mile per hour or kilometers per hour 1351 */ 1352 protected void setUnits() { 1353 if (mphButton.isSelected()) { 1354 profileGraphPane.setUnitsMph(); 1355 basicSpeedMatchTargetStartSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1356 basicSpeedMatchTargetHighSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1357 speedStepScaleSpeedMatchMaxSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1358 speedStepScaleMaxSpeedActualUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1359 } else { 1360 profileGraphPane.setUnitsKph(); 1361 basicSpeedMatchTargetStartSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1362 basicSpeedMatchTargetHighSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1363 speedStepScaleSpeedMatchMaxSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1364 speedStepScaleMaxSpeedActualUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1365 } 1366 profileGraphPane.repaint(); 1367 if (mphButton.isSelected()) { 1368 speedoDialDisplay.setUnitsMph(); 1369 } else { 1370 speedoDialDisplay.setUnitsKph(); 1371 } 1372 speedoDialDisplay.update(currentSpeed); 1373 speedoDialDisplay.repaint(); 1374 } 1375 1376 /** 1377 * Validate the users custom scale entry. 1378 */ 1379 protected void getCustomScale() { 1380 if (selectedScale == -1) { 1381 try { 1382 customScale = Integer.parseUnsignedInt(customScaleField.getText()); 1383 } catch (NumberFormatException ex) { 1384 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CustomScaleDialog"), 1385 Bundle.getMessage("CustomScaleTitle"), JmriJOptionPane.ERROR_MESSAGE); 1386 } 1387 } 1388 } 1389 //</editor-fold> 1390 1391 //<editor-fold defaultstate="collapsed" desc="Address Helper Functions"> 1392 /** 1393 * Handle changing/setting the address. 1394 */ 1395 private synchronized void changeOfAddress() { 1396 if (addrSelector.getAddress() != null) { 1397 locomotiveAddress = addrSelector.getAddress(); 1398 setTitle(); 1399 } else { 1400 locomotiveAddress = new DccLocoAddress(0, true); 1401 setTitle(); 1402 } 1403 } 1404 1405 /** 1406 * Set the RosterEntry for this throttle. 1407 * 1408 * @param entry roster entry selected for throttle 1409 */ 1410 public void setRosterEntry(RosterEntry entry) { 1411 rosterBox.setSelectedItem(entry); 1412 addrSelector.setAddress(entry.getDccLocoAddress()); 1413 rosterEntry = entry; 1414 changeOfAddress(); 1415 } 1416 1417 /** 1418 * Called when a RosterEntry is selected 1419 */ 1420 private void rosterItemSelected() { 1421 if (rosterBox.getSelectedRosterEntries().length != 0) { 1422 setRosterEntry(rosterBox.getSelectedRosterEntries()[0]); 1423 } 1424 } 1425 //</editor-fold> 1426 1427 //<editor-fold defaultstate="collapsed" desc="Power Manager Helper Functions"> 1428 /** 1429 * {@inheritDoc} 1430 * <p> 1431 * Handles property changes from the power manager. 1432 */ 1433 @Override 1434 public void propertyChange(PropertyChangeEvent evt) { 1435 setPowerStatus(); 1436 } 1437 1438 /** 1439 * Switches the track power on or off 1440 */ 1441 private void setPowerStatus() { 1442 if (pm == null) { 1443 return; 1444 } 1445 if (pm.getPower() == PowerManager.ON) { 1446 trackPowerButton.setText(Bundle.getMessage("PowerDown")); 1447 //statusLabel.setText(Bundle.getMessage("StatTOn")); 1448 } else if (pm.getPower() == PowerManager.OFF) { 1449 trackPowerButton.setText(Bundle.getMessage("PowerUp")); 1450 //statusLabel.setText(Bundle.getMessage("StatTOff")); 1451 } 1452 } 1453 1454 /** 1455 * Called when the track power button is clicked to turn on or off track 1456 * power Allows user to power up and give time for sound decoder startup 1457 * sequence before running a profile 1458 */ 1459 protected void trackPower() { 1460 try { 1461 if (pm.getPower() != PowerManager.ON) { 1462 pm.setPower(PowerManager.ON); 1463 } else { 1464 stopProfileAndSpeedMatch(); 1465 pm.setPower(PowerManager.OFF); 1466 } 1467 } catch (JmriException e) { 1468 log.error("Exception during power on: {}", e.toString()); 1469 } 1470 } 1471 //</editor-fold> 1472 1473 //<editor-fold defaultstate="collapsed" desc="Speed Profiling"> 1474 javax.swing.Timer profileTimer = null; 1475 1476 /** 1477 * Start the speed profiling process 1478 */ 1479 protected synchronized void startProfile() { 1480 if (locomotiveAddress.getNumber() > 0) { 1481 if (dirFwdButton.isSelected() || dirRevButton.isSelected()) { 1482 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1483 profileTimer = new javax.swing.Timer(4000, e -> profileTimeout()); 1484 profileTimer.setRepeats(false); 1485 profileState = ProfileState.WAIT_FOR_THROTTLE; 1486 // Request a throttle 1487 statusLabel.setText(Bundle.getMessage("StatReqThrottle")); 1488 spFwd.clear(); 1489 spRev.clear(); 1490 if (dirFwdButton.isSelected()) { 1491 profileDir = ProfileDirection.FORWARD; 1492 } else { 1493 profileDir = ProfileDirection.REVERSE; 1494 } 1495 resetGraphButton.setEnabled(false); 1496 profileGraphPane.repaint(); 1497 profileTimer.start(); 1498 log.info("Requesting throttle"); 1499 boolean requestOK = jmri.InstanceManager.throttleManagerInstance().requestThrottle(locomotiveAddress, this, true); 1500 if (!requestOK) { 1501 log.error("Loco Address in use, throttle request failed."); 1502 } 1503 } 1504 } 1505 } else { 1506 // Must have a non-zero address 1507 //profileAddressField.setBackground(Color.RED); 1508 log.error("Attempt to profile loco address 0"); 1509 } 1510 } 1511 1512 /** 1513 * Profile timer timeout handler 1514 */ 1515 protected synchronized void profileTimeout() { 1516 switch (profileState) { 1517 case WAIT_FOR_THROTTLE: 1518 tidyUp(); 1519 log.error("Timeout waiting for throttle"); 1520 statusLabel.setText(Bundle.getMessage("StatusTimeout")); 1521 break; 1522 case RUNNING: 1523 if (profileDir == ProfileDirection.FORWARD) { 1524 spFwd.setPoint(profileStep, avSpeed); 1525 statusLabel.setText(Bundle.getMessage("Fwd", profileStep)); 1526 } else { 1527 spRev.setPoint(profileStep, avSpeed); 1528 statusLabel.setText(Bundle.getMessage("Rev", profileStep)); 1529 } 1530 profileGraphPane.repaint(); 1531 if (profileStep == 29) { 1532 if ((profileDir == ProfileDirection.FORWARD) 1533 && dirRevButton.isSelected()) { 1534 // Start reverse profile 1535 profileDir = ProfileDirection.REVERSE; 1536 throttle.setIsForward(false); 1537 profileStep = 0; 1538 avClr(); 1539 statusLabel.setText(Bundle.getMessage("StatCreateRev")); 1540 } else { 1541 tidyUp(); 1542 statusLabel.setText(Bundle.getMessage("StatDone")); 1543 } 1544 } else { 1545 if (profileStep == 28) { 1546 profileSpeed = 0.0F; 1547 } else { 1548 profileSpeed += throttleIncrement; 1549 } 1550 throttle.setSpeedSetting(profileSpeed); 1551 profileStep += 1; 1552 // adjust delay as we get faster and averaging is quicker 1553 profileTimer.setDelay(7000 - range * 1000); 1554 } 1555 break; 1556 default: 1557 log.error("Unexpected profile timeout"); 1558 profileTimer.stop(); 1559 break; 1560 } 1561 } 1562 //</editor-fold> 1563 1564 //<editor-fold defaultstate="collapsed" desc="Speed Profiling and Speed Matching Cleanup"> 1565 /** 1566 * Resets profiling and speed matching timers and other pertinent values and 1567 * releases the throttle and ops mode programmer 1568 * <p> 1569 * Called both when profiling or speed matching finish successfully or error 1570 * out 1571 */ 1572 protected void tidyUp() { 1573 stopTimers(); 1574 1575 //turn off power 1576 //Turning power off is bad for some systems, e.g. Digitrax 1577// try { 1578// pm.setPower(PowerManager.OFF); 1579// } catch (JmriException e) { 1580// log.error("Exception during power off: "+e.toString()); 1581// } 1582 //release throttle 1583 if (throttle != null) { 1584 throttle.setSpeedSetting(0.0F); 1585 InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this); 1586 throttle = null; 1587 } 1588 1589 //clean up speed matcher 1590 if (speedMatcher != null) { 1591 speedMatcher.stopSpeedMatcher(); 1592 speedMatcher = null; 1593 } 1594 1595 resetGraphButton.setEnabled(true); 1596 progState = ProgState.IDLE; 1597 profileState = ProfileState.IDLE; 1598 } 1599 1600 /** 1601 * Stops the profiling and speed matching processes. Called by pressing 1602 * either the stop profile or stop speed matching buttons. 1603 */ 1604 protected synchronized void stopProfileAndSpeedMatch() { 1605 if (profileState != ProfileState.IDLE || !speedMatcher.isSpeedMatcherIdle()) { 1606 if (profileState != ProfileState.IDLE) { 1607 log.info("Profiling/Speed Matching stopped by user"); 1608 } 1609 1610 tidyUp(); 1611 } 1612 } 1613 1614 /** 1615 * Stops profile and speed match timers 1616 */ 1617 protected void stopTimers() { 1618 if (profileTimer != null) { 1619 profileTimer.stop(); 1620 } 1621 } 1622 //</editor-fold> 1623 1624 //<editor-fold defaultstate="collapsed" desc="Notifiers"> 1625 /** 1626 * Called when a throttle is found 1627 * 1628 * @param t the requested DccThrottle 1629 */ 1630 @Override 1631 public synchronized void notifyThrottleFound(DccThrottle t) { 1632 stopTimers(); 1633 1634 throttle = t; 1635 log.info("Throttle acquired"); 1636 throttle.setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 1637 if (throttle.getSpeedStepMode() != SpeedStepMode.NMRA_DCC_28) { 1638 log.error("Failed to set 28 step mode"); 1639 statusLabel.setText(Bundle.getMessage("ThrottleError28")); 1640 InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this); 1641 return; 1642 } 1643 1644 // turn on power 1645 try { 1646 pm.setPower(PowerManager.ON); 1647 } catch (JmriException e) { 1648 log.error("Exception during power on: {}", e.toString()); 1649 return; 1650 } 1651 1652 throttleIncrement = throttle.getSpeedIncrement(); 1653 1654 if (profileState == ProfileState.WAIT_FOR_THROTTLE) { 1655 log.info("Starting profiling"); 1656 profileState = ProfileState.RUNNING; 1657 // Start at step 0 with 28 step packets 1658 profileSpeed = 0.0F; 1659 profileStep = 0; 1660 throttle.setSpeedSetting(profileSpeed); 1661 if (profileDir == ProfileDirection.FORWARD) { 1662 throttle.setIsForward(true); 1663 statusLabel.setText(Bundle.getMessage("StatCreateFwd")); 1664 } else { 1665 throttle.setIsForward(false); 1666 statusLabel.setText(Bundle.getMessage("StatCreateRev")); 1667 } 1668 // using profile timer to trigger each next step 1669 profileTimer.setRepeats(true); 1670 profileTimer.start(); 1671 } else { 1672 tidyUp(); 1673 } 1674 } 1675 1676 /** 1677 * Called when a throttle could not be obtained 1678 * 1679 * @param address the requested address 1680 * @param reason the reason the throttle could not be obtained 1681 */ 1682 @Override 1683 public void notifyFailedThrottleRequest(jmri.LocoAddress address, String reason) { 1684 } 1685 1686 /** 1687 * Called when we must decide to steal the throttle for the requested 1688 * address. Since this is a an automatically stealing implementation, the 1689 * throttle will be automatically stolen. 1690 */ 1691 @Override 1692 public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) { 1693 InstanceManager.throttleManagerInstance().responseThrottleDecision(address, this, DecisionType.STEAL); 1694 } 1695 //</editor-fold> 1696 1697 //<editor-fold defaultstate="collapsed" desc="Other Timers"> 1698 javax.swing.Timer replyTimer = null; 1699 javax.swing.Timer displayTimer = null; 1700 javax.swing.Timer fastDisplayTimer = null; 1701 1702 /** 1703 * Starts the speedo hardware reply timer. Once we receive a speedoReply we 1704 * expect them regularly, at least once every 4 seconds. 1705 */ 1706 protected void startReplyTimer() { 1707 replyTimer = new javax.swing.Timer(4000, e -> replyTimeout()); 1708 replyTimer.setRepeats(true); // refresh until stopped by dispose 1709 replyTimer.start(); 1710 } 1711 1712 /** 1713 * Starts the timer used to update the speedometer display speed. 1714 */ 1715 protected void startDisplayTimer() { 1716 displayTimer = new javax.swing.Timer(DISPLAY_UPDATE, e -> displayTimeout()); 1717 displayTimer.setRepeats(true); // refresh until stopped by dispose 1718 displayTimer.start(); 1719 } 1720 1721 /** 1722 * Starts the timer used to update the speedometer display speed at a faster 1723 * rate. 1724 */ 1725 protected void startFastDisplayTimer() { 1726 fastDisplayTimer = new javax.swing.Timer(DISPLAY_UPDATE / FAST_DISPLAY_RATIO, e -> fastDisplayTimeout()); 1727 fastDisplayTimer.setRepeats(true); // refresh until stopped by dispose 1728 fastDisplayTimer.start(); 1729 } 1730 1731 //<editor-fold defaultstate="collapsed" desc="Timer Timeout Handlers"> 1732 /** 1733 * Internal routine to reset the speed on a timeout. 1734 */ 1735 protected synchronized void replyTimeout() { 1736 //log.debug("Timed out - display speed zero"); 1737 targetSpeed = 0; 1738 avClr(); 1739 oldSpeed = 0; 1740 showSpeed(); 1741 } 1742 1743 /** 1744 * Internal routine to update the target speed for display 1745 */ 1746 protected synchronized void displayTimeout() { 1747 //log.info("Display timeout"); 1748 targetSpeed = avSpeed; 1749 incSpeed = (targetSpeed - currentSpeed) / FAST_DISPLAY_RATIO; 1750 } 1751 1752 /** 1753 * Internal routine to update the displayed speed 1754 */ 1755 protected synchronized void fastDisplayTimeout() { 1756 //log.info("Display timeout"); 1757 if (Math.abs(targetSpeed - currentSpeed) < Math.abs(incSpeed)) { 1758 currentSpeed = targetSpeed; 1759 } else { 1760 1761 currentSpeed += incSpeed; 1762 } 1763 if (currentSpeed < 0.01F) { 1764 currentSpeed = 0.0F; 1765 } 1766 1767 showSpeed(); 1768 1769 if (speedMatcher != null) { 1770 speedMatcher.updateCurrentSpeed(currentSpeed); 1771 } 1772 } 1773 1774 /** 1775 * Timeout requesting a throttle. 1776 */ 1777 protected synchronized void throttleTimeout() { 1778 jmri.InstanceManager.throttleManagerInstance().cancelThrottleRequest(locomotiveAddress, this); 1779 profileState = ProfileState.IDLE; 1780 log.error("Timeout waiting for throttle"); 1781 } 1782 //</editor-fold> 1783 //</editor-fold> 1784 1785 //<editor-fold defaultstate="collapsed" desc="Programming Functions"> 1786 /** 1787 * Starts reading the address (CVs 29 then 1 (short) or 17 and 18 (long)) 1788 * using the service mode programmer 1789 */ 1790 protected void readAddress() { 1791 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1792 progState = ProgState.READ29; 1793 statusLabel.setText(Bundle.getMessage("ProgRd29")); 1794 startRead("29"); 1795 } 1796 } 1797 1798 /** 1799 * Starts reading the momentum CVs (CV 3 and 4) using the global programmer 1800 */ 1801 protected void readMomentum() { 1802 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1803 progState = ProgState.READ3; 1804 statusLabel.setText(Bundle.getMessage("ProgReadAccel")); 1805 startRead("3"); 1806 } 1807 } 1808 1809 /** 1810 * Starts writing the momentum CVs (CV 3 and 4) using the global programmer 1811 */ 1812 protected void setMomentum() { 1813 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1814 progState = ProgState.WRITE3; 1815 int acceleration = accelerationSM.getNumber().intValue(); 1816 statusLabel.setText(Bundle.getMessage("ProgSetAccel", acceleration)); 1817 startWrite("3", acceleration); 1818 } 1819 } 1820 1821 /** 1822 * Starts reading a CV using the service mode programmer 1823 * 1824 * @param cv the CV 1825 */ 1826 protected void startRead(String cv) { 1827 try { 1828 prog.readCV(cv, this); 1829 } catch (ProgrammerException e) { 1830 log.error("Exception reading CV {}", cv, e); 1831 } 1832 } 1833 1834 /** 1835 * STarts writing a CV using the global programmer 1836 * 1837 * @param cv the CV 1838 * @param value the value to write to the CV 1839 */ 1840 protected void startWrite(String cv, int value) { 1841 try { 1842 prog.writeCV(cv, value, this); 1843 } catch (ProgrammerException e) { 1844 log.error("Exception setting CV {} to {}", cv, value, e); 1845 } 1846 } 1847 1848 /** 1849 * Called when the programmer (ops mode or service mode) has completed its 1850 * operation 1851 * 1852 * @param value Value from a read operation, or value written on a write 1853 * @param status Denotes the completion code. Note that this is a bitwise 1854 * combination of the various states codes defined in this 1855 * interface. (see ProgListener.java for possible values) 1856 */ 1857 @Override 1858 public void programmingOpReply(int value, int status) { 1859 if (status == 0) { 1860 switch (progState) { 1861 case IDLE: 1862 log.debug("unexpected reply in IDLE state"); 1863 break; 1864 1865 case READ29: 1866 // Check extended address bit 1867 if ((value & 0x20) == 0) { 1868 progState = ProgState.READ1; 1869 statusLabel.setText(Bundle.getMessage("ProgRdShort")); 1870 startRead("1"); 1871 } else { 1872 progState = ProgState.READ17; 1873 statusLabel.setText(Bundle.getMessage("ProgRdExtended")); 1874 startRead("17"); 1875 } 1876 break; 1877 1878 case READ1: 1879 readAddress = value; 1880 //profileAddressField.setText(Integer.toString(profileAddress)); 1881 //profileAddressField.setBackground(Color.WHITE); 1882 addrSelector.setAddress(new DccLocoAddress(readAddress, false)); 1883 changeOfAddress(); 1884 progState = ProgState.IDLE; 1885 break; 1886 1887 case READ3: 1888 accelerationSM.setValue(value); 1889 progState = ProgState.READ4; 1890 statusLabel.setText(Bundle.getMessage("ProgReadDecel")); 1891 startRead("4"); 1892 break; 1893 1894 case READ4: 1895 decelerationSM.setValue(value); 1896 progState = ProgState.IDLE; 1897 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1898 break; 1899 1900 case READ17: 1901 readAddress = value; 1902 progState = ProgState.READ18; 1903 startRead("18"); 1904 break; 1905 1906 case READ18: 1907 readAddress = (readAddress & 0x3f) * 256 + value; 1908 //profileAddressField.setText(Integer.toString(profileAddress)); 1909 //profileAddressField.setBackground(Color.WHITE); 1910 addrSelector.setAddress(new DccLocoAddress(readAddress, true)); 1911 changeOfAddress(); 1912 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1913 progState = ProgState.IDLE; 1914 break; 1915 1916 case WRITE3: 1917 progState = ProgState.WRITE4; 1918 int deceleration = decelerationSM.getNumber().intValue(); 1919 statusLabel.setText(Bundle.getMessage("ProgSetDecel", deceleration)); 1920 startWrite("4", deceleration); 1921 break; 1922 1923 case WRITE4: 1924 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1925 progState = ProgState.IDLE; 1926 break; 1927 1928 default: 1929 progState = ProgState.IDLE; 1930 log.warn("Unhandled read state: {}", progState); 1931 break; 1932 } 1933 } else { 1934 // Error during programming 1935 log.error("Status not OK during {}: {}", progState.toString(), status); 1936 //profileAddressField.setText("Error"); 1937 statusLabel.setText(Bundle.getMessage("ProgError")); 1938 progState = ProgState.IDLE; 1939 tidyUp(); 1940 } 1941 } 1942 //</editor-fold> 1943 1944 //debugging logger 1945 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SpeedoConsoleFrame.class); 1946}