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 private final 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 case 200: 1177 circ = 12.5664F; 1178 readerLabel.setText(Bundle.getMessage("Reader200")); 1179 break; 1180 default: 1181 speedTextField.setText(Bundle.getMessage("ReaderErr")); 1182 log.error("Invalid reader type"); 1183 break; 1184 } 1185 1186 // Update speed 1187 calcSpeed(); 1188 } 1189 if (timerRunning == false) { 1190 // first reply starts the timer 1191 startReplyTimer(); 1192 startDisplayTimer(); 1193 startFastDisplayTimer(); 1194 timerRunning = true; 1195 } else { 1196 // subsequent replies restart it 1197 replyTimer.restart(); 1198 } 1199 } 1200 1201 /** 1202 * Calculates the scale speed in KPH 1203 */ 1204 protected void calcSpeed() { 1205 float thisScale = (selectedScale == -1) ? customScale : selectedScale; 1206 if (series == 103) { 1207 // KPF-Zeller 1208 // calculate kph: r/sec * circumference converted to hours and kph in scaleFace() 1209 sampleSpeed = (float) ((count / 8.) * circ * 3600 / 1.0E6 * thisScale * SPEED_TEST_SCALE_FACTOR); 1210 // data arrives at constant rate, so we don't average nor switch range 1211 avSpeed = sampleSpeed; 1212 log.debug("New KPF-Zeller sample: {} Average: {}", sampleSpeed, avSpeed); 1213 1214 } else if (series == 200) { 1215 // SPC200R 1216 sampleSpeed = count / 10.0f; 1217 avSpeed = sampleSpeed; 1218 log.debug("New SPC200R sample: {} Average: {}", sampleSpeed, avSpeed); 1219 1220 } else if (series > 0 && series <= 6) { 1221 // Bachrus 1222 // Scale the data and calculate kph 1223 try { 1224 freq = 1500000 / count; 1225 sampleSpeed = (freq / 24) * circ * thisScale * 3600 / 1000000 * SPEED_TEST_SCALE_FACTOR; 1226 } catch (ArithmeticException ae) { 1227 log.error("Exception calculating sampleSpeed", ae); 1228 } 1229 avFn(sampleSpeed); 1230 log.debug("New Bachrus sample: {} Average: {}", sampleSpeed, avSpeed); 1231 log.debug("Acc: {} range: {}", acc, range); 1232 switchRange(); 1233 } 1234 } 1235 1236 /** 1237 * Calculates the average speed using a filter 1238 * 1239 * @param speed the speed of the latest interation 1240 */ 1241 protected void avFn(float speed) { 1242 // Averaging function used for speed is 1243 // S(t) = S(t-1) - [S(t-1)/N] + speed 1244 // A(t) = S(t)/N 1245 // 1246 // where S is an accumulator, N is the length of the filter (i.e., 1247 // the number of samples included in the rolling average), and A is 1248 // the result of the averaging function. 1249 // 1250 // Re-arranged 1251 // S(t) = S(t-1) - A(t-1) + speed 1252 // A(t) = S(t)/N 1253 acc = acc - avSpeed + speed; 1254 avSpeed = acc / FILTER_LENGTH[range]; 1255 } 1256 1257 /** 1258 * Clears the average speed calculation 1259 */ 1260 protected void avClr() { 1261 acc = 0; 1262 avSpeed = 0; 1263 } 1264 1265 /** 1266 * Switches the filter used for averaging speed based on the measured speed 1267 */ 1268 protected void switchRange() { 1269 // When we switch range we must compensate the current accumulator 1270 // value for the longer filter. 1271 switch (range) { 1272 case 1: 1273 if (sampleSpeed > RANGE1HI) { 1274 range++; 1275 acc = acc * FILTER_LENGTH[2] / FILTER_LENGTH[1]; 1276 } 1277 break; 1278 case 2: 1279 if (sampleSpeed < RANGE2LO) { 1280 range--; 1281 acc = acc * FILTER_LENGTH[1] / FILTER_LENGTH[2]; 1282 } else if (sampleSpeed > RANGE2HI) { 1283 range++; 1284 acc = acc * FILTER_LENGTH[3] / FILTER_LENGTH[2]; 1285 } 1286 break; 1287 case 3: 1288 if (sampleSpeed < RANGE3LO) { 1289 range--; 1290 acc = acc * FILTER_LENGTH[2] / FILTER_LENGTH[3]; 1291 } else if (sampleSpeed > RANGE3HI) { 1292 range++; 1293 acc = acc * FILTER_LENGTH[4] / FILTER_LENGTH[3]; 1294 } 1295 break; 1296 case 4: 1297 if (sampleSpeed < RANGE4LO) { 1298 range--; 1299 acc = acc * FILTER_LENGTH[3] / FILTER_LENGTH[4]; 1300 } 1301 break; 1302 default: 1303 log.debug("range {} unsupported, range unchanged.", range); 1304 } 1305 } 1306 1307 /** 1308 * Displays the speed in the SpeedoConsoleFrame's digital/analog speedometer 1309 */ 1310 protected void showSpeed() { 1311 float speedForText = currentSpeed; 1312 if (mphButton.isSelected()) { 1313 speedForText = Speed.kphToMph(speedForText); 1314 } 1315 if (series > 0) { 1316 if ((currentSpeed < 0) || (currentSpeed > 999)) { 1317 log.error("Calculated speed out of range: {}", currentSpeed); 1318 speedTextField.setText("999"); 1319 } else { 1320 // Final smoothing as applied by Bachrus Console. Don't update display 1321 // unless speed has changed more than 2% 1322 if ((currentSpeed > oldSpeed * 1.02) || (currentSpeed < oldSpeed * 0.98)) { 1323 speedTextField.setText(MessageFormat.format("{0,number,##0.0}", speedForText)); 1324 speedTextField.setHorizontalAlignment(JTextField.RIGHT); 1325 oldSpeed = currentSpeed; 1326 speedoDialDisplay.update(currentSpeed); 1327 } 1328 } 1329 } 1330 } 1331 //</editor-fold> 1332 1333 //<editor-fold defaultstate="collapsed" desc="Speedometer Helper Functions"> 1334 /** 1335 * Check if custom scale selected and enable the custom scale entry field. 1336 */ 1337 protected void checkCustomScale() { 1338 if (selectedScale == -1) { 1339 customScaleField.setEnabled(true); 1340 } else { 1341 customScaleField.setEnabled(false); 1342 } 1343 } 1344 1345 /** 1346 * Set the speed to be displayed as a dial or numeric 1347 */ 1348 protected void setDial() { 1349 CardLayout cl = (CardLayout) displayCards.getLayout(); 1350 if (numButton.isSelected()) { 1351 display = DisplayType.NUMERIC; 1352 cl.show(displayCards, "NUMERIC"); 1353 } else { 1354 display = DisplayType.DIAL; 1355 cl.show(displayCards, "DIAL"); 1356 } 1357 } 1358 1359 /** 1360 * Set the displays to mile per hour or kilometers per hour 1361 */ 1362 protected void setUnits() { 1363 if (mphButton.isSelected()) { 1364 profileGraphPane.setUnitsMph(); 1365 basicSpeedMatchTargetStartSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1366 basicSpeedMatchTargetHighSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1367 speedStepScaleSpeedMatchMaxSpeedUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1368 speedStepScaleMaxSpeedActualUnit.setText(Bundle.getMessage("SpeedMatchMPHLabel")); 1369 } else { 1370 profileGraphPane.setUnitsKph(); 1371 basicSpeedMatchTargetStartSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1372 basicSpeedMatchTargetHighSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1373 speedStepScaleSpeedMatchMaxSpeedUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1374 speedStepScaleMaxSpeedActualUnit.setText(Bundle.getMessage("SpeedMatchKPHLabel")); 1375 } 1376 profileGraphPane.repaint(); 1377 if (mphButton.isSelected()) { 1378 speedoDialDisplay.setUnitsMph(); 1379 } else { 1380 speedoDialDisplay.setUnitsKph(); 1381 } 1382 speedoDialDisplay.update(currentSpeed); 1383 speedoDialDisplay.repaint(); 1384 } 1385 1386 /** 1387 * Validate the users custom scale entry. 1388 */ 1389 protected void getCustomScale() { 1390 if (selectedScale == -1) { 1391 try { 1392 customScale = Integer.parseUnsignedInt(customScaleField.getText()); 1393 } catch (NumberFormatException ex) { 1394 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CustomScaleDialog"), 1395 Bundle.getMessage("CustomScaleTitle"), JmriJOptionPane.ERROR_MESSAGE); 1396 } 1397 } 1398 } 1399 //</editor-fold> 1400 1401 //<editor-fold defaultstate="collapsed" desc="Address Helper Functions"> 1402 /** 1403 * Handle changing/setting the address. 1404 */ 1405 private synchronized void changeOfAddress() { 1406 if (addrSelector.getAddress() != null) { 1407 locomotiveAddress = addrSelector.getAddress(); 1408 setTitle(); 1409 } else { 1410 locomotiveAddress = new DccLocoAddress(0, true); 1411 setTitle(); 1412 } 1413 } 1414 1415 /** 1416 * Set the RosterEntry for this throttle. 1417 * 1418 * @param entry roster entry selected for throttle 1419 */ 1420 public void setRosterEntry(RosterEntry entry) { 1421 rosterBox.setSelectedItem(entry); 1422 addrSelector.setAddress(entry.getDccLocoAddress()); 1423 rosterEntry = entry; 1424 changeOfAddress(); 1425 } 1426 1427 /** 1428 * Called when a RosterEntry is selected 1429 */ 1430 private void rosterItemSelected() { 1431 if (rosterBox.getSelectedRosterEntries().length != 0) { 1432 setRosterEntry(rosterBox.getSelectedRosterEntries()[0]); 1433 } 1434 } 1435 //</editor-fold> 1436 1437 //<editor-fold defaultstate="collapsed" desc="Power Manager Helper Functions"> 1438 /** 1439 * {@inheritDoc} 1440 * <p> 1441 * Handles property changes from the power manager. 1442 */ 1443 @Override 1444 public void propertyChange(PropertyChangeEvent evt) { 1445 setPowerStatus(); 1446 } 1447 1448 /** 1449 * Switches the track power on or off 1450 */ 1451 private void setPowerStatus() { 1452 if (pm == null) { 1453 return; 1454 } 1455 if (pm.getPower() == PowerManager.ON) { 1456 trackPowerButton.setText(Bundle.getMessage("PowerDown")); 1457 //statusLabel.setText(Bundle.getMessage("StatTOn")); 1458 } else if (pm.getPower() == PowerManager.OFF) { 1459 trackPowerButton.setText(Bundle.getMessage("PowerUp")); 1460 //statusLabel.setText(Bundle.getMessage("StatTOff")); 1461 } 1462 } 1463 1464 /** 1465 * Called when the track power button is clicked to turn on or off track 1466 * power Allows user to power up and give time for sound decoder startup 1467 * sequence before running a profile 1468 */ 1469 protected void trackPower() { 1470 try { 1471 if (pm.getPower() != PowerManager.ON) { 1472 pm.setPower(PowerManager.ON); 1473 } else { 1474 stopProfileAndSpeedMatch(); 1475 pm.setPower(PowerManager.OFF); 1476 } 1477 } catch (JmriException e) { 1478 log.error("Exception during power on: {}", e.toString()); 1479 } 1480 } 1481 //</editor-fold> 1482 1483 //<editor-fold defaultstate="collapsed" desc="Speed Profiling"> 1484 javax.swing.Timer profileTimer = null; 1485 1486 /** 1487 * Start the speed profiling process 1488 */ 1489 protected synchronized void startProfile() { 1490 if (locomotiveAddress.getNumber() > 0) { 1491 if (dirFwdButton.isSelected() || dirRevButton.isSelected()) { 1492 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1493 profileTimer = new javax.swing.Timer(4000, e -> profileTimeout()); 1494 profileTimer.setRepeats(false); 1495 profileState = ProfileState.WAIT_FOR_THROTTLE; 1496 // Request a throttle 1497 statusLabel.setText(Bundle.getMessage("StatReqThrottle")); 1498 spFwd.clear(); 1499 spRev.clear(); 1500 if (dirFwdButton.isSelected()) { 1501 profileDir = ProfileDirection.FORWARD; 1502 } else { 1503 profileDir = ProfileDirection.REVERSE; 1504 } 1505 resetGraphButton.setEnabled(false); 1506 profileGraphPane.repaint(); 1507 profileTimer.start(); 1508 log.info("Requesting throttle"); 1509 boolean requestOK = jmri.InstanceManager.throttleManagerInstance().requestThrottle(locomotiveAddress, this, true); 1510 if (!requestOK) { 1511 log.error("Loco Address in use, throttle request failed."); 1512 } 1513 } 1514 } 1515 } else { 1516 // Must have a non-zero address 1517 //profileAddressField.setBackground(Color.RED); 1518 log.error("Attempt to profile loco address 0"); 1519 } 1520 } 1521 1522 /** 1523 * Profile timer timeout handler 1524 */ 1525 protected synchronized void profileTimeout() { 1526 switch (profileState) { 1527 case WAIT_FOR_THROTTLE: 1528 tidyUp(); 1529 log.error("Timeout waiting for throttle"); 1530 statusLabel.setText(Bundle.getMessage("StatusTimeout")); 1531 break; 1532 case RUNNING: 1533 if (profileDir == ProfileDirection.FORWARD) { 1534 spFwd.setPoint(profileStep, avSpeed); 1535 statusLabel.setText(Bundle.getMessage("Fwd", profileStep)); 1536 } else { 1537 spRev.setPoint(profileStep, avSpeed); 1538 statusLabel.setText(Bundle.getMessage("Rev", profileStep)); 1539 } 1540 profileGraphPane.repaint(); 1541 if (profileStep == 29) { 1542 if ((profileDir == ProfileDirection.FORWARD) 1543 && dirRevButton.isSelected()) { 1544 // Start reverse profile 1545 profileDir = ProfileDirection.REVERSE; 1546 throttle.setIsForward(false); 1547 profileStep = 0; 1548 avClr(); 1549 statusLabel.setText(Bundle.getMessage("StatCreateRev")); 1550 } else { 1551 tidyUp(); 1552 statusLabel.setText(Bundle.getMessage("StatDone")); 1553 } 1554 } else { 1555 if (profileStep == 28) { 1556 profileSpeed = 0.0F; 1557 } else { 1558 profileSpeed += throttleIncrement; 1559 } 1560 throttle.setSpeedSetting(profileSpeed); 1561 profileStep += 1; 1562 // adjust delay as we get faster and averaging is quicker 1563 profileTimer.setDelay(7000 - range * 1000); 1564 } 1565 break; 1566 default: 1567 log.error("Unexpected profile timeout"); 1568 profileTimer.stop(); 1569 break; 1570 } 1571 } 1572 //</editor-fold> 1573 1574 //<editor-fold defaultstate="collapsed" desc="Speed Profiling and Speed Matching Cleanup"> 1575 /** 1576 * Resets profiling and speed matching timers and other pertinent values and 1577 * releases the throttle and ops mode programmer 1578 * <p> 1579 * Called both when profiling or speed matching finish successfully or error 1580 * out 1581 */ 1582 protected void tidyUp() { 1583 stopTimers(); 1584 1585 //turn off power 1586 //Turning power off is bad for some systems, e.g. Digitrax 1587// try { 1588// pm.setPower(PowerManager.OFF); 1589// } catch (JmriException e) { 1590// log.error("Exception during power off: "+e.toString()); 1591// } 1592 //release throttle 1593 if (throttle != null) { 1594 throttle.setSpeedSetting(0.0F); 1595 InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this); 1596 throttle = null; 1597 } 1598 1599 //clean up speed matcher 1600 if (speedMatcher != null) { 1601 speedMatcher.stopSpeedMatcher(); 1602 speedMatcher = null; 1603 } 1604 1605 resetGraphButton.setEnabled(true); 1606 progState = ProgState.IDLE; 1607 profileState = ProfileState.IDLE; 1608 } 1609 1610 /** 1611 * Stops the profiling and speed matching processes. Called by pressing 1612 * either the stop profile or stop speed matching buttons. 1613 */ 1614 protected synchronized void stopProfileAndSpeedMatch() { 1615 if (profileState != ProfileState.IDLE || !speedMatcher.isSpeedMatcherIdle()) { 1616 if (profileState != ProfileState.IDLE) { 1617 log.info("Profiling/Speed Matching stopped by user"); 1618 } 1619 1620 tidyUp(); 1621 } 1622 } 1623 1624 /** 1625 * Stops profile and speed match timers 1626 */ 1627 protected void stopTimers() { 1628 if (profileTimer != null) { 1629 profileTimer.stop(); 1630 } 1631 } 1632 //</editor-fold> 1633 1634 //<editor-fold defaultstate="collapsed" desc="Notifiers"> 1635 /** 1636 * Called when a throttle is found 1637 * 1638 * @param t the requested DccThrottle 1639 */ 1640 @Override 1641 public synchronized void notifyThrottleFound(DccThrottle t) { 1642 stopTimers(); 1643 1644 throttle = t; 1645 log.info("Throttle acquired"); 1646 throttle.setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 1647 if (throttle.getSpeedStepMode() != SpeedStepMode.NMRA_DCC_28) { 1648 log.error("Failed to set 28 step mode"); 1649 statusLabel.setText(Bundle.getMessage("ThrottleError28")); 1650 InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this); 1651 return; 1652 } 1653 1654 // turn on power 1655 try { 1656 pm.setPower(PowerManager.ON); 1657 } catch (JmriException e) { 1658 log.error("Exception during power on: {}", e.toString()); 1659 return; 1660 } 1661 1662 throttleIncrement = throttle.getSpeedIncrement(); 1663 1664 if (profileState == ProfileState.WAIT_FOR_THROTTLE) { 1665 log.info("Starting profiling"); 1666 profileState = ProfileState.RUNNING; 1667 // Start at step 0 with 28 step packets 1668 profileSpeed = 0.0F; 1669 profileStep = 0; 1670 throttle.setSpeedSetting(profileSpeed); 1671 if (profileDir == ProfileDirection.FORWARD) { 1672 throttle.setIsForward(true); 1673 statusLabel.setText(Bundle.getMessage("StatCreateFwd")); 1674 } else { 1675 throttle.setIsForward(false); 1676 statusLabel.setText(Bundle.getMessage("StatCreateRev")); 1677 } 1678 // using profile timer to trigger each next step 1679 profileTimer.setRepeats(true); 1680 profileTimer.start(); 1681 } else { 1682 tidyUp(); 1683 } 1684 } 1685 1686 /** 1687 * Called when a throttle could not be obtained 1688 * 1689 * @param address the requested address 1690 * @param reason the reason the throttle could not be obtained 1691 */ 1692 @Override 1693 public void notifyFailedThrottleRequest(jmri.LocoAddress address, String reason) { 1694 } 1695 1696 /** 1697 * Called when we must decide to steal the throttle for the requested 1698 * address. Since this is a an automatically stealing implementation, the 1699 * throttle will be automatically stolen. 1700 */ 1701 @Override 1702 public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) { 1703 InstanceManager.throttleManagerInstance().responseThrottleDecision(address, this, DecisionType.STEAL); 1704 } 1705 //</editor-fold> 1706 1707 //<editor-fold defaultstate="collapsed" desc="Other Timers"> 1708 javax.swing.Timer replyTimer = null; 1709 javax.swing.Timer displayTimer = null; 1710 javax.swing.Timer fastDisplayTimer = null; 1711 1712 /** 1713 * Starts the speedo hardware reply timer. Once we receive a speedoReply we 1714 * expect them regularly, at least once every 4 seconds. 1715 */ 1716 protected void startReplyTimer() { 1717 replyTimer = new javax.swing.Timer(4000, e -> replyTimeout()); 1718 replyTimer.setRepeats(true); // refresh until stopped by dispose 1719 replyTimer.start(); 1720 } 1721 1722 /** 1723 * Starts the timer used to update the speedometer display speed. 1724 */ 1725 protected void startDisplayTimer() { 1726 displayTimer = new javax.swing.Timer(DISPLAY_UPDATE, e -> displayTimeout()); 1727 displayTimer.setRepeats(true); // refresh until stopped by dispose 1728 displayTimer.start(); 1729 } 1730 1731 /** 1732 * Starts the timer used to update the speedometer display speed at a faster 1733 * rate. 1734 */ 1735 protected void startFastDisplayTimer() { 1736 fastDisplayTimer = new javax.swing.Timer(DISPLAY_UPDATE / FAST_DISPLAY_RATIO, e -> fastDisplayTimeout()); 1737 fastDisplayTimer.setRepeats(true); // refresh until stopped by dispose 1738 fastDisplayTimer.start(); 1739 } 1740 1741 //<editor-fold defaultstate="collapsed" desc="Timer Timeout Handlers"> 1742 /** 1743 * Internal routine to reset the speed on a timeout. 1744 */ 1745 protected synchronized void replyTimeout() { 1746 //log.debug("Timed out - display speed zero"); 1747 targetSpeed = 0; 1748 avClr(); 1749 oldSpeed = 0; 1750 showSpeed(); 1751 } 1752 1753 /** 1754 * Internal routine to update the target speed for display 1755 */ 1756 protected synchronized void displayTimeout() { 1757 //log.info("Display timeout"); 1758 targetSpeed = avSpeed; 1759 incSpeed = (targetSpeed - currentSpeed) / FAST_DISPLAY_RATIO; 1760 } 1761 1762 /** 1763 * Internal routine to update the displayed speed 1764 */ 1765 protected synchronized void fastDisplayTimeout() { 1766 //log.info("Display timeout"); 1767 if (Math.abs(targetSpeed - currentSpeed) < Math.abs(incSpeed)) { 1768 currentSpeed = targetSpeed; 1769 } else { 1770 1771 currentSpeed += incSpeed; 1772 } 1773 if (currentSpeed < 0.01F) { 1774 currentSpeed = 0.0F; 1775 } 1776 1777 showSpeed(); 1778 1779 if (speedMatcher != null) { 1780 speedMatcher.updateCurrentSpeed(currentSpeed); 1781 } 1782 } 1783 1784 /** 1785 * Timeout requesting a throttle. 1786 */ 1787 protected synchronized void throttleTimeout() { 1788 jmri.InstanceManager.throttleManagerInstance().cancelThrottleRequest(locomotiveAddress, this); 1789 profileState = ProfileState.IDLE; 1790 log.error("Timeout waiting for throttle"); 1791 } 1792 //</editor-fold> 1793 //</editor-fold> 1794 1795 //<editor-fold defaultstate="collapsed" desc="Programming Functions"> 1796 /** 1797 * Starts reading the address (CVs 29 then 1 (short) or 17 and 18 (long)) 1798 * using the service mode programmer 1799 */ 1800 protected void readAddress() { 1801 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1802 progState = ProgState.READ29; 1803 statusLabel.setText(Bundle.getMessage("ProgRd29")); 1804 startRead("29"); 1805 } 1806 } 1807 1808 /** 1809 * Starts reading the momentum CVs (CV 3 and 4) using the global programmer 1810 */ 1811 protected void readMomentum() { 1812 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1813 progState = ProgState.READ3; 1814 statusLabel.setText(Bundle.getMessage("ProgReadAccel")); 1815 startRead("3"); 1816 } 1817 } 1818 1819 /** 1820 * Starts writing the momentum CVs (CV 3 and 4) using the global programmer 1821 */ 1822 protected void setMomentum() { 1823 if ((speedMatcher == null || speedMatcher.isSpeedMatcherIdle()) && (profileState == ProfileState.IDLE)) { 1824 progState = ProgState.WRITE3; 1825 int acceleration = accelerationSM.getNumber().intValue(); 1826 statusLabel.setText(Bundle.getMessage("ProgSetAccel", acceleration)); 1827 startWrite("3", acceleration); 1828 } 1829 } 1830 1831 /** 1832 * Starts reading a CV using the service mode programmer 1833 * 1834 * @param cv the CV 1835 */ 1836 protected void startRead(String cv) { 1837 try { 1838 prog.readCV(cv, this); 1839 } catch (ProgrammerException e) { 1840 log.error("Exception reading CV {}", cv, e); 1841 } 1842 } 1843 1844 /** 1845 * STarts writing a CV using the global programmer 1846 * 1847 * @param cv the CV 1848 * @param value the value to write to the CV 1849 */ 1850 protected void startWrite(String cv, int value) { 1851 try { 1852 prog.writeCV(cv, value, this); 1853 } catch (ProgrammerException e) { 1854 log.error("Exception setting CV {} to {}", cv, value, e); 1855 } 1856 } 1857 1858 /** 1859 * Called when the programmer (ops mode or service mode) has completed its 1860 * operation 1861 * 1862 * @param value Value from a read operation, or value written on a write 1863 * @param status Denotes the completion code. Note that this is a bitwise 1864 * combination of the various states codes defined in this 1865 * interface. (see ProgListener.java for possible values) 1866 */ 1867 @Override 1868 public void programmingOpReply(int value, int status) { 1869 if (status == 0) { 1870 switch (progState) { 1871 case IDLE: 1872 log.debug("unexpected reply in IDLE state"); 1873 break; 1874 1875 case READ29: 1876 // Check extended address bit 1877 if ((value & 0x20) == 0) { 1878 progState = ProgState.READ1; 1879 statusLabel.setText(Bundle.getMessage("ProgRdShort")); 1880 startRead("1"); 1881 } else { 1882 progState = ProgState.READ17; 1883 statusLabel.setText(Bundle.getMessage("ProgRdExtended")); 1884 startRead("17"); 1885 } 1886 break; 1887 1888 case READ1: 1889 readAddress = value; 1890 //profileAddressField.setText(Integer.toString(profileAddress)); 1891 //profileAddressField.setBackground(Color.WHITE); 1892 addrSelector.setAddress(new DccLocoAddress(readAddress, false)); 1893 changeOfAddress(); 1894 progState = ProgState.IDLE; 1895 break; 1896 1897 case READ3: 1898 accelerationSM.setValue(value); 1899 progState = ProgState.READ4; 1900 statusLabel.setText(Bundle.getMessage("ProgReadDecel")); 1901 startRead("4"); 1902 break; 1903 1904 case READ4: 1905 decelerationSM.setValue(value); 1906 progState = ProgState.IDLE; 1907 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1908 break; 1909 1910 case READ17: 1911 readAddress = value; 1912 progState = ProgState.READ18; 1913 startRead("18"); 1914 break; 1915 1916 case READ18: 1917 readAddress = (readAddress & 0x3f) * 256 + value; 1918 //profileAddressField.setText(Integer.toString(profileAddress)); 1919 //profileAddressField.setBackground(Color.WHITE); 1920 addrSelector.setAddress(new DccLocoAddress(readAddress, true)); 1921 changeOfAddress(); 1922 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1923 progState = ProgState.IDLE; 1924 break; 1925 1926 case WRITE3: 1927 progState = ProgState.WRITE4; 1928 int deceleration = decelerationSM.getNumber().intValue(); 1929 statusLabel.setText(Bundle.getMessage("ProgSetDecel", deceleration)); 1930 startWrite("4", deceleration); 1931 break; 1932 1933 case WRITE4: 1934 statusLabel.setText(Bundle.getMessage("ProgRdComplete")); 1935 progState = ProgState.IDLE; 1936 break; 1937 1938 default: 1939 progState = ProgState.IDLE; 1940 log.warn("Unhandled read state: {}", progState); 1941 break; 1942 } 1943 } else { 1944 // Error during programming 1945 log.error("Status not OK during {}: {}", progState.toString(), status); 1946 //profileAddressField.setText("Error"); 1947 statusLabel.setText(Bundle.getMessage("ProgError")); 1948 progState = ProgState.IDLE; 1949 tidyUp(); 1950 } 1951 } 1952 //</editor-fold> 1953 1954 //debugging logger 1955 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SpeedoConsoleFrame.class); 1956}