001package jmri.jmrit.roster.swing.speedprofile; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Component; 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.awt.event.ActionEvent; 009import java.beans.PropertyChangeEvent; 010import java.beans.PropertyChangeListener; 011import java.util.ArrayList; 012import java.util.List; 013import java.util.Map; 014import java.util.TreeMap; 015 016import javax.swing.BorderFactory; 017import javax.swing.Box; 018import javax.swing.BoxLayout; 019import javax.swing.JButton; 020import javax.swing.JLabel; 021import javax.swing.JPanel; 022import javax.swing.JTextField; 023 024import jmri.Block; 025import jmri.DccThrottle; 026import jmri.InstanceManager; 027import jmri.Sensor; 028import jmri.SensorManager; 029import jmri.SpeedStepMode; 030import jmri.ThrottleListener; 031import jmri.jmrit.logix.WarrantPreferences; 032import jmri.jmrit.roster.Roster; 033import jmri.jmrit.roster.RosterEntry; 034import jmri.jmrit.roster.RosterSpeedProfile; 035import jmri.jmrit.roster.swing.RosterEntryComboBox; 036import jmri.profile.ProfileManager; 037import jmri.profile.ProfileUtils; 038import jmri.util.jdom.JDOMUtil; 039import jmri.util.swing.BeanSelectCreatePanel; 040import jmri.util.swing.JmriJOptionPane; 041 042import org.jdom2.Element; 043import org.jdom2.JDOMException; 044 045/** 046 * Set up and run automated speed table calibration. 047 * <p> 048 * Uses three sensors in a row (see diagram in window help): 049 * <ul> 050 * <li>Start sensor: Track where locomotive starts 051 * <li>Block sensor: Middle track. This time through this is used to measure the 052 * speed. 053 * <li>Finish sensor: Track where locomotive stops before repeating. 054 * </ul> 055 * The expected sequence is: 056 * <ul> 057 * <li>Start moving with Start sensor on, others off. 058 * <li>Block (middle) sensor goes active: startListener calls startTiming 059 * <li>Finish sensor goes active: finishListener calls stopCurrentSpeedStep 060 * <li>Block (middle) sensor goes inactive: startListener calls stopLoco, which 061 * stops loco after 2.5 seconds 062 * </ul> 063 * After a forward run, the Start and Finish sensors are swapped for a run in 064 * reverse. 065 */ 066class SpeedProfilePanel extends jmri.util.swing.JmriPanel implements ThrottleListener { 067 068 public static final String XML_ROOT = "speedprofiler-config"; 069 public static final String XML_NAMESPACE = "http://jmri.org/xml/schema/speedometer-3-9-3.xsd"; 070 JButton profileButton = new JButton(Bundle.getMessage("ButtonStart")); 071 JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 072 JButton testButton = new JButton(Bundle.getMessage("ButtonTest")); 073 JButton testCancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 074 JButton clearNewDataButton = new JButton(Bundle.getMessage("ButtonClearNewData")); 075 JButton viewNewButton = new JButton(Bundle.getMessage("ButtonViewNew")); 076 JButton viewMergedButton = new JButton(Bundle.getMessage("ButtonViewMerged")); 077 JButton viewButton = new JButton(Bundle.getMessage("ButtonViewCurrent")); 078 079 JButton updateProfileButton = new JButton(Bundle.getMessage("ButtonUpdateProfile")); 080 JButton replaceProfileButton = new JButton(Bundle.getMessage("ButtonSaveProfile")); 081 JButton deleteProfileButton = new JButton(Bundle.getMessage("ButtonDeleteProfile")); 082 JButton saveDefaultsButton = new JButton(Bundle.getMessage("ButtonSaveDefaults")); 083 JTextField lengthField = new JTextField(10); 084 JTextField sensorDelay = new JTextField(5); 085 JTextField speedStepTest = new JTextField(5); 086 JTextField speedStepTestFwd = new JTextField(10); 087 JTextField speedStepTestRev = new JTextField(10); 088 JTextField speedStepFrom = new JTextField(5); 089 JTextField speedStepTo = new JTextField(5); 090 JTextField speedStepIncr = new JTextField(5); 091 JLabel warrentScaleLabel = new JLabel(); 092 093 // Start or finish sensor 094 BeanSelectCreatePanel<Sensor> sensorAPanel = new BeanSelectCreatePanel<>(InstanceManager.sensorManagerInstance(), null); 095 096 // Finish or start sensor 097 BeanSelectCreatePanel<Sensor> sensorBPanel = new BeanSelectCreatePanel<>(InstanceManager.sensorManagerInstance(), null); 098 099 // Block sensor 100 BeanSelectCreatePanel<Block> blockCPanel = new BeanSelectCreatePanel<>(InstanceManager.getDefault(jmri.BlockManager.class), null); 101 BeanSelectCreatePanel<Sensor> sensorCPanel = new BeanSelectCreatePanel<>(InstanceManager.sensorManagerInstance(), null); 102 103 RosterEntryComboBox reBox = new RosterEntryComboBox(); 104 SpeedProfileTable table = null; 105 boolean profile = false; 106 boolean test = false; 107 float testSpeedFwd = 0.0f; 108 float testSpeedRev = 0.0f; 109 boolean save = false; 110 boolean unmergedNewData = false; // true if new data has been gathered but not merged to profile 111 boolean unsavedUpdatedProfile = false; // true if the roster profile has been updated but not saved 112 113 private JLabel sourceLabel; 114 115 public SpeedProfilePanel() { 116 JPanel main = new JPanel(); 117 118 GridBagLayout gb = new GridBagLayout(); 119 GridBagConstraints c = new GridBagConstraints(); 120 main.setLayout(gb); 121 122 c.gridx = 0; 123 c.gridy = 0; 124 c.weightx = 1.0; 125 c.anchor = GridBagConstraints.CENTER; 126 JLabel label = new JLabel(Bundle.getMessage("LabelLengthOfBlock")); 127 addRow(main, gb, c, 0, label, lengthField); 128 label = new JLabel(Bundle.getMessage("LabelSensorDelay")); 129 addRow(main, gb, c, 1, label, sensorDelay); 130 label = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelStartSensor"))); 131 addRow(main, gb, c, 2, label, sensorAPanel); 132 label = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelBlockSensor"))); 133 addRow(main, gb, c, 3, label, sensorCPanel); 134 label = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelFinishSensor"))); 135 addRow(main, gb, c, 4, label, sensorBPanel); 136 label = new JLabel(Bundle.getMessage("LabelSelectRoster")); 137 JPanel left = makePadPanel(label); 138 JPanel right = makePadPanel(reBox); 139 addRow(main, gb, c, 5, left, right); 140 JPanel panelViews = new JPanel(); 141 panelViews.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TitleView"))); 142 panelViews.setLayout(new BoxLayout(panelViews, BoxLayout.LINE_AXIS)); 143 panelViews.add(clearNewDataButton); 144 panelViews.add(viewNewButton); 145 panelViews.add(viewMergedButton); 146 panelViews.add(viewButton); 147 left = makePadPanel(panelViews); 148 JPanel panelProfileControl = new JPanel(); 149 panelProfileControl.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("ButtonProfile"))); 150 panelProfileControl.setLayout(new BoxLayout(panelProfileControl, BoxLayout.LINE_AXIS)); 151 panelProfileControl.add(profileButton); 152 panelProfileControl.add(cancelButton); 153 right = makePadPanel(panelProfileControl); 154 addRow(main, gb, c, 6, left, right); 155 156 left = new JPanel(); 157 left.add(Box.createRigidArea(new java.awt.Dimension(20, 10))); 158 left.setLayout(new BoxLayout(left, BoxLayout.PAGE_AXIS)); 159 left.add(makeLabelPanel("LabelStartStep", speedStepFrom)); 160 speedStepFrom.setToolTipText(Bundle.getMessage("StartStepToolTip")); 161 left.add(makeLabelPanel("LabelFinishStep", speedStepTo)); 162 speedStepTo.setToolTipText(Bundle.getMessage("FinishStepToolTip")); 163 left.add(makeLabelPanel("LabelStepIncr", speedStepIncr)); 164 speedStepIncr.setToolTipText(Bundle.getMessage("StepIncrToolTip")); 165 right = new JPanel(); 166 addRow(main, gb, c, 7, left, right); 167 168 JPanel testDataPanel = new JPanel(); 169 testDataPanel.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TestProfileData"))); 170 testDataPanel.setLayout(new BoxLayout(testDataPanel, BoxLayout.LINE_AXIS)); 171 testDataPanel.add(makeLabelPanel("LabelTestStep", speedStepTest)); 172 speedStepTest.setToolTipText(Bundle.getMessage("StepTestToolTip")); 173 speedStepTestFwd.setEnabled(false); 174 testDataPanel.add(makeLabelPanel("LabelTestStepFwd", speedStepTestFwd)); 175 speedStepTestFwd.setToolTipText(Bundle.getMessage("ForwardTestToolTip")); 176 speedStepTestRev.setEnabled(false); 177 testDataPanel.add(makeLabelPanel("LabelTestStepRev", speedStepTestRev)); 178 speedStepTestRev.setToolTipText(Bundle.getMessage("ReverseTestToolTip")); 179 left = makePadPanel(testDataPanel); 180 181 JPanel testProfileControl = new JPanel(); 182 testProfileControl.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TitleTestProfile"))); 183 testProfileControl.setLayout(new BoxLayout(testProfileControl, BoxLayout.LINE_AXIS)); 184 testProfileControl.add(testButton); 185 testProfileControl.add(testCancelButton); 186 right = makePadPanel(testProfileControl); 187 188 addRow(main, gb, c, 8, left, right); 189 190 c.fill = GridBagConstraints.HORIZONTAL; 191 c.gridx = 0; 192 c.gridy = 9; 193 c.gridwidth = 2; 194 sourceLabel = new JLabel(" "); 195 sourceLabel.setBackground(Color.white); 196 left = makePadPanel(sourceLabel); 197 gb.setConstraints(left, c); 198 main.add(left); 199 200 WarrantPreferences preferences = WarrantPreferences.getDefault(); 201 warrentScaleLabel.setText(Bundle.getMessage("LabelLayoutScale") + " 1:" + Float.toString(preferences.getLayoutScale())); 202 warrentScaleLabel.setBackground(Color.white); 203 warrentScaleLabel.setToolTipText(Bundle.getMessage("LayoutScaleHint")); 204 left = makePadPanel(warrentScaleLabel); 205 c.gridy = 11; 206 gb.setConstraints(left, c); 207 main.add(left); 208 209 c.gridy = 12; 210 JPanel southBtnPanel = new JPanel(); 211 southBtnPanel.add(clearNewDataButton); 212 southBtnPanel.add(updateProfileButton); 213 southBtnPanel.add(replaceProfileButton); 214 southBtnPanel.add(deleteProfileButton); 215 southBtnPanel.add(saveDefaultsButton); 216 main.add(southBtnPanel, c); 217 218 add(main, BorderLayout.CENTER); 219 220 profileButton.addActionListener((ActionEvent e) -> { 221 profile = true; 222 setupProfile(); 223 }); 224 cancelButton.addActionListener((ActionEvent e) -> { 225 cancelButton(); 226 }); 227 testButton.addActionListener((ActionEvent e) -> { 228 test = true; 229 testButton(); 230 }); 231 testCancelButton.addActionListener((ActionEvent e) -> { 232 cancelButton(); 233 }); 234 viewButton.addActionListener((ActionEvent e) -> { 235 viewRosterProfileData(); 236 }); 237 238 viewNewButton.addActionListener((ActionEvent e) -> { 239 viewNewProfileData(); 240 }); 241 242 saveDefaultsButton.addActionListener((ActionEvent e) -> { 243 doSaveSettings(); 244 }); 245 clearNewDataButton.addActionListener((ActionEvent e) -> { 246 clearNewData(); 247 }); 248 viewMergedButton.addActionListener((ActionEvent e) -> { 249 viewMergedData(); 250 }); 251 updateProfileButton.addActionListener((ActionEvent e) -> { 252 updateSpeedProfileWithResults(); 253 }); 254 replaceProfileButton.addActionListener((ActionEvent e) -> { 255 removeSpeedProfile(); 256 updateSpeedProfileWithResults(); 257 }); 258 deleteProfileButton.addActionListener((ActionEvent e) -> { 259 removeSpeedProfile(); 260 }); 261 262 setButtonStates(true); 263 // Attempt to reload last values */ 264 doLoad(); 265 266 } 267 268 static void addRow(JPanel main, GridBagLayout gb, GridBagConstraints c, int row, Component left, Component right) { 269 c.gridx = 0; 270 c.gridy = row; 271 gb.setConstraints(left, c); 272 main.add(left); 273 c.gridx = 1; 274 gb.setConstraints(right, c); 275 main.add(right); 276 } 277 278 static JPanel makePadPanel(Component comp) { 279 JPanel panel = new JPanel(); 280 panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 281 panel.add(Box.createRigidArea(new java.awt.Dimension(20, 20))); 282 panel.add(comp); 283 return panel; 284 } 285 286 static JPanel makeLabelPanel(String text, Component comp) { 287 JPanel panel = new JPanel(); 288 panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); 289 panel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage(text)))); 290 panel.add(comp); 291 return panel; 292 } 293 294 SensorDetails sensorA; 295 SensorDetails sensorB; 296 RosterEntry re; 297 DccThrottle t; 298 int finishSpeedStep; 299 protected int stepIncr; 300 protected int profileStep; 301 protected float profileSpeed; 302 protected float profileIncrement; 303 protected int profileSpeedStepMode; 304 protected float profileSensorDelay; 305 protected float profileBlockLength; 306 RosterSpeedProfile rosterSpeedProfile; 307 308 protected float profileSpeedAtStart; 309 310 void setupProfile() { 311 String text; 312 finishSpeedStep = 0; 313 stepIncr = 1; 314 profileStep = 1; 315 profileSensorDelay = 0.0f; 316 try { 317 profileBlockLength = Float.parseFloat(lengthField.getText()); 318 } catch (Exception e) { 319 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorLengthInvalid")); 320 return; 321 } 322 text = sensorDelay.getText(); 323 if (text != null && text.trim().length() > 0) { 324 try { 325 profileSensorDelay = Float.parseFloat(sensorDelay.getText()); 326 } catch (Exception e) { 327 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorDelayInvalid")); 328 return; 329 } 330 } 331 setButtonStates(false); 332 if (sensorA == null) { 333 try { 334 sensorA = new SensorDetails(sensorAPanel.getNamedBean()); 335 } catch (Exception e) { 336 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelStartSensor"))); 337 setButtonStates(true); 338 return; 339 } 340 } else { 341 Sensor tmpSen = null; 342 try { 343 tmpSen = sensorAPanel.getNamedBean(); 344 } catch (Exception e) { 345 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelStartSensor"))); 346 setButtonStates(true); 347 return; 348 } 349 if (tmpSen != sensorA.getSensor()) { 350 sensorA.resetDetails(); 351 sensorA = new SensorDetails(tmpSen); 352 } 353 } 354 if (sensorB == null) { 355 try { 356 sensorB = new SensorDetails(sensorBPanel.getNamedBean()); 357 } catch (Exception e) { 358 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelFinishSensor"))); 359 setButtonStates(true); 360 return; 361 } 362 363 } else { 364 Sensor tmpSen = null; 365 try { 366 tmpSen = sensorBPanel.getNamedBean(); 367 } catch (Exception e) { 368 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelFinishSensor"))); 369 setButtonStates(true); 370 return; 371 } 372 if (tmpSen != sensorB.getSensor()) { 373 sensorB.resetDetails(); 374 sensorB = new SensorDetails(tmpSen); 375 } 376 } 377 if (middleBlockSensor == null) { 378 try { 379 middleBlockSensor = new SensorDetails(sensorCPanel.getNamedBean()); 380 } catch (Exception e) { 381 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelBlockSensor"))); 382 setButtonStates(true); 383 return; 384 } 385 } else { 386 Sensor tmpSen = null; 387 try { 388 tmpSen = sensorCPanel.getNamedBean(); 389 } catch (Exception e) { 390 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSensorNotFound", Bundle.getMessage("LabelBlockSensor"))); 391 setButtonStates(true); 392 return; 393 } 394 if (tmpSen != middleBlockSensor.getSensor()) { 395 middleBlockSensor.resetDetails(); 396 middleBlockSensor = new SensorDetails(tmpSen); 397 } 398 } 399 if (reBox.getSelectedRosterEntries().length == 0) { 400 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoRosterSelected")); 401 log.warn("No Roster Entry selected."); 402 setButtonStates(true); 403 return; 404 } 405 text = speedStepFrom.getText(); 406 if (text != null && text.trim().length() > 0) { 407 try { 408 profileStep = Integer.parseInt(text); 409 if (!speedStepNumOK(profileStep, "LabelStartStep")) { 410 setButtonStates(true); 411 return; 412 } 413 } catch (Exception e) { 414 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSpeedStep", Bundle.getMessage("LabelStartStep"))); 415 setButtonStates(true); 416 return; 417 } 418 } 419 text = speedStepTo.getText(); 420 if (text != null && text.trim().length() > 0) { 421 try { 422 finishSpeedStep = Integer.parseInt(text); 423 if (!speedStepNumOK(finishSpeedStep, "LabelFinishStep")) { 424 setButtonStates(true); 425 return; 426 } 427 } catch (Exception e) { 428 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSpeedStep", Bundle.getMessage("LabelFinishStep"))); 429 setButtonStates(true); 430 return; 431 } 432 } 433 text = speedStepIncr.getText(); 434 if (text != null && text.trim().length() > 0) { 435 try { 436 stepIncr = Integer.parseInt(text); 437 if (!speedStepNumOK(stepIncr, "LabelStepIncr")) { 438 setButtonStates(true); 439 return; 440 } 441 } catch (Exception e) { 442 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSpeedStep", Bundle.getMessage("LabelStepIncr"))); 443 setButtonStates(true); 444 return; 445 } 446 } 447 448 throttleState = 0; 449 re = reBox.getSelectedRosterEntries()[0]; 450 boolean ok = InstanceManager.throttleManagerInstance().requestThrottle(re, this, true); // we have a mechanism for steal / share 451 if (!ok) { 452 log.warn("Throttle for locomotive {} could not be set up.", re.getId()); 453 setButtonStates(true); 454 return; 455 } 456 // Wait for throttle be correct and then run the profile 457 jmri.util.ThreadingUtil.newThread(new Runnable() { 458 @Override 459 public void run() { 460 int count = 0; 461 int trys = 10; 462 while (throttleState == 0 && count < trys) { 463 try { 464 Thread.sleep(1000); 465 log.debug("Wait"); 466 } catch (Exception ex) { 467 log.warn("Throttle for locomotive {} could not be set up.", re.getId()); 468 setButtonStates(true); 469 return; 470 } 471 count++; 472 } 473 log.debug("Run"); 474 if (throttleState != 1) { 475 log.warn("No Throttle, Aborting"); 476 setButtonStates(true); 477 return; 478 } 479 runProfile(); 480 } 481 }).start(); 482 483 } 484 485 boolean speedStepNumOK(int num, String step) { 486 if (num < 1 || num > 126) { 487 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorSpeedStep", Bundle.getMessage(step))); 488 setButtonStates(true); 489 return false; 490 } 491 return true; 492 } 493 494 javax.swing.Timer overRunTimer = null; 495 496 private volatile int throttleState = 0; // zero waiting, -1 no throttle (message already shown), 1 497 498 @Override 499 public void notifyThrottleFound(DccThrottle _throttle) { 500 t = _throttle; 501 if (t == null) { 502 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorThrottleNotFound")); 503 log.warn("null throttle returned for train {} during automatic initialization.", re.getId()); 504 setButtonStates(true); 505 throttleState = -1; 506 return; 507 } 508 if (log.isDebugEnabled()) { 509 log.debug("throttle address = {}", t.getLocoAddress().toString()); 510 } 511 throttleState = 1; 512 } 513 514 private void runProfile() { 515 SpeedStepMode speedStepMode = t.getSpeedStepMode(); 516 profileIncrement = t.getSpeedIncrement(); 517 profileSpeedStepMode = speedStepMode.numSteps; 518 if (finishSpeedStep <= 0) { 519 finishSpeedStep = profileSpeedStepMode; 520 } 521 522 log.debug("Speed step mode {}", profileSpeedStepMode); 523 profileSpeed = profileIncrement * profileStep; 524 525 profileSpeedAtStart = profileSpeed; 526 527 if (profile) { 528 startSensor = middleBlockSensor.getSensor(); 529 finishSensor = sensorB.getSensor(); 530 startListener = new PropertyChangeListener() { 531 @Override 532 public void propertyChange(PropertyChangeEvent e) { 533 if (e.getPropertyName().equals("KnownState")) { 534 if (((Integer) e.getNewValue()) == Sensor.ACTIVE) { 535 startTiming(); 536 } 537 if (((Integer) e.getNewValue()) == Sensor.INACTIVE) { 538 stopLoco(); 539 } 540 } 541 } 542 }; 543 finishListener = new PropertyChangeListener() { 544 @Override 545 public void propertyChange(PropertyChangeEvent e) { 546 if (e.getPropertyName().equals("KnownState")) { 547 if (((Integer) e.getNewValue()) == Sensor.ACTIVE) { 548 stopCurrentSpeedStep(); 549 } 550 } 551 } 552 }; 553 554 isForward = true; 555 startProfile(); 556 } else { 557 // Speed test. 558 // Once back and forth 559 stepIncr = 1; 560 profileStep = Integer.parseInt(speedStepTest.getText()); 561 finishSpeedStep = profileStep; 562 profileSpeed = profileIncrement * profileStep; 563 startSensor = middleBlockSensor.getSensor(); 564 finishSensor = sensorB.getSensor(); 565 startListener = new PropertyChangeListener() { 566 @Override 567 public void propertyChange(PropertyChangeEvent e) { 568 if (e.getPropertyName().equals("KnownState")) { 569 if (((Integer) e.getNewValue()) == Sensor.ACTIVE) { 570 startTiming(); 571 } 572 if (((Integer) e.getNewValue()) == Sensor.INACTIVE) { 573 stopLoco(); 574 } 575 } 576 } 577 }; 578 finishListener = new PropertyChangeListener() { 579 @Override 580 public void propertyChange(PropertyChangeEvent e) { 581 if (e.getPropertyName().equals("KnownState")) { 582 if (((Integer) e.getNewValue()) == Sensor.ACTIVE) { 583 stopCurrentSpeedStep(); 584 } 585 } 586 } 587 }; 588 589 isForward = true; 590 startProfile(); 591 } 592 } 593 594 void setButtonStates(boolean state) { 595 cancelButton.setEnabled(!state); 596 profileButton.setEnabled(state); 597 testButton.setEnabled(state); 598 testCancelButton.setEnabled(!state); 599 viewButton.setEnabled(state); 600 deleteProfileButton.setEnabled(state); 601 if (state && speeds.size() > 0) { 602 viewNewButton.setEnabled(true); 603 viewMergedButton.setEnabled(true); 604 replaceProfileButton.setEnabled(true); 605 updateProfileButton.setEnabled(true); 606 clearNewDataButton.setEnabled(true); 607 } else { 608 viewNewButton.setEnabled(false); 609 viewMergedButton.setEnabled(false); 610 replaceProfileButton.setEnabled(false); 611 updateProfileButton.setEnabled(false); 612 clearNewDataButton.setEnabled(false); 613 } 614 if (state) { 615 sourceLabel.setText(" "); 616 profile = false; 617 test = false; 618 } 619 if (sensorA != null) { 620 sensorA.resetDetails(); 621 } 622 if (sensorB != null) { 623 sensorB.resetDetails(); 624 } 625 if (middleBlockSensor != null) { 626 middleBlockSensor.resetDetails(); 627 } 628 } 629 630 @Override 631 public void notifyFailedThrottleRequest(jmri.LocoAddress address, String reason) { 632 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorFailThrottleRequest")); 633 log.error("Throttle request for {} failed because {}", address, reason); 634 setButtonStates(true); 635 throttleState = -1; 636 } 637 638 /** 639 * Profiling on a stolen or shared throttle is invalid 640 * <p> 641 * {@inheritDoc} 642 */ 643 @Override 644 public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) { 645 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoStealing")); 646 InstanceManager.throttleManagerInstance().cancelThrottleRequest(address, this); 647 setButtonStates(true); 648 throttleState = -1; 649 } 650 651 PropertyChangeListener startListener = null; 652 PropertyChangeListener finishListener = null; 653 PropertyChangeListener middleListener = null; 654 655 Sensor startSensor; 656 Sensor finishSensor; 657 SensorDetails middleBlockSensor; 658 659 void startProfile() { 660 stepCalculated = false; 661 sourceLabel.setText(Bundle.getMessage("StatusLabelNextRun")); 662 if (isForward) { 663 finishSensor = sensorB.getSensor(); 664 } else { 665 finishSensor = sensorA.getSensor(); 666 } 667 startSensor = middleBlockSensor.getSensor(); 668 startSensor.addPropertyChangeListener(startListener); 669 finishSensor.addPropertyChangeListener(finishListener); 670 t.setIsForward(!isForward); 671 // this switching back and forward helps if the throttle was stolen. 672 // the sleeps are needed as some systems dont like a speed setting right after a direction setting. 673 // If we had guarenteed access to the Dispatcher frame we could use 674 // Thread.sleep(InstanceManager.getDefault(DispatcherFrame.class).getMinThrottleInterval() * 2) 675 try { 676 Thread.sleep(250); 677 } catch (InterruptedException e) { 678 // Nothing I can do. 679 } 680 681 t.setIsForward(isForward); 682 try { 683 Thread.sleep(250); 684 } catch (InterruptedException e) { 685 // Nothing I can do. 686 } 687 688 log.debug("Set speed to [{}] isForward [{}] Increment [{}] Step [{}] SpeedStepMode [{}]", 689 profileSpeed, isForward, profileIncrement, profileStep, profileSpeedStepMode); 690 t.setSpeedSetting(profileSpeed); 691 sourceLabel.setText(Bundle.getMessage("StatusLabelBlockToGoActive")); 692 } 693 694 boolean isForward = true; 695 696 void startTiming() { 697 startTime = System.nanoTime(); 698 sourceLabel.setText(Bundle.getMessage("StatusLabelCurrentRun", 699 (isForward ? Bundle.getMessage("LabelTestStepFwd") : Bundle.getMessage("LabelTestStepRev")), 700 profileStep, finishSpeedStep)); 701 } 702 703 boolean stepCalculated = false; 704 705 void stopCurrentSpeedStep() { 706 finishTime = System.nanoTime(); 707 stepCalculated = true; 708 finishSensor.removePropertyChangeListener(finishListener); 709 sourceLabel.setText(Bundle.getMessage("StatusLabelCalculating")); 710 711 if (profileSpeed/2 > profileSpeedAtStart) { 712 t.setSpeedSetting(profileSpeed / 2); 713 } else { 714 t.setSpeedSetting(profileSpeedAtStart); 715 } 716 717 calculateSpeed(); 718 sourceLabel.setText(Bundle.getMessage("StatusLabelWaitingToClear")); 719 } 720 721 void stopLoco() { 722 723 if (!stepCalculated) { 724 return; 725 } 726 727 startSensor.removePropertyChangeListener(startListener); 728 finishSensor.removePropertyChangeListener(finishListener); 729 730 isForward = !isForward; 731 if (isForward) { 732 profileSpeed = profileIncrement * stepIncr + profileSpeed; 733 profileStep += stepIncr; 734 } 735 736 if (profileStep > finishSpeedStep) { 737 t.setSpeedSetting(0.0f); 738 if (!profile) { 739 // there are only the 2 fields on screen to be updated after a test 740 speedStepTestFwd.setText(RosterSpeedProfile.convertMMSToScaleSpeedWithUnits(testSpeedFwd)); 741 speedStepTestRev.setText(RosterSpeedProfile.convertMMSToScaleSpeedWithUnits(testSpeedRev)); 742 } 743 releaseThrottle(); 744 //updateSpeedProfileWithResults(); 745 setButtonStates(true); 746 return; 747 } 748 // Loco may have been brought to half-speed in stopCurrentSpeedStep, so wait for that to take effect then stop & restart 749 javax.swing.Timer stopTimer = new javax.swing.Timer(2500, new java.awt.event.ActionListener() { 750 @Override 751 public void actionPerformed(java.awt.event.ActionEvent e) { 752 753 // finally command the stop 754 t.setSpeedSetting(0.0f); 755 // and a second later, restart going the other way 756 javax.swing.Timer restartTimer = new javax.swing.Timer(1000, new java.awt.event.ActionListener() { 757 @Override 758 public void actionPerformed(java.awt.event.ActionEvent e) { 759 startProfile(); 760 } 761 }); 762 restartTimer.setRepeats(false); 763 restartTimer.start(); 764 } 765 }); 766 stopTimer.setRepeats(false); 767 stopTimer.start(); 768 } 769 770 void calculateSpeed() { 771 float duration = (((float) (finishTime - startTime)) / 1000000000); // convert to seconds 772 duration = duration - (profileSensorDelay / 1000); // allow for time differences between sensor delays 773 float speed = profileBlockLength / duration; 774 log.debug("Step: {} duration: {} length: {} speed: {}", 775 profileStep, duration, profileBlockLength, speed); 776 777 778 if (profile) { 779 // save results to table 780 int iSpeedStep = Math.round(profileSpeed * 1000); 781 if (!speeds.containsKey(iSpeedStep)) { 782 speeds.put(iSpeedStep, new SpeedStep()); 783 } 784 SpeedStep ss = speeds.get(iSpeedStep); 785 if (isForward) { 786 ss.setForwardSpeed(speed); 787 } else { 788 ss.setReverseSpeed(speed); 789 } 790 save = true; 791 } else { 792 // testing, save results to the 2 fields. 793 if (isForward) { 794 testSpeedFwd = speed; 795 } else { 796 testSpeedRev = speed; 797 } 798 } 799 } 800 801 /** 802 * Merge the new data into the existing speedprofile, or create if not 803 * current, and save. Clear new data. 804 */ 805 void updateSpeedProfileWithResults() { 806 cancelButton(); 807 RosterSpeedProfile rosterSpeedProfile = re.getSpeedProfile(); 808 if (rosterSpeedProfile == null) { 809 rosterSpeedProfile = new RosterSpeedProfile(re); 810 re.setSpeedProfile(rosterSpeedProfile); 811 } 812 for (Map.Entry<Integer, SpeedStep> entry : speeds.entrySet()) { 813 rosterSpeedProfile.setSpeed(entry.getKey(), entry.getValue().getForwardSpeed(), entry.getValue().getReverseSpeed()); 814 } 815 re.updateFile(); 816 Roster.getDefault().writeRoster(); 817 clearNewData(); 818 setButtonStates(true); 819 save = false; 820 } 821 822 /** 823 * Merge the current profile with the new data in a temp area and show. 824 */ 825 void viewMergedData() { 826 // create a new temporay rosterspeedentry 827 RosterEntry tmpRe = new RosterEntry(); 828 RosterSpeedProfile tmpRsp = new RosterSpeedProfile(tmpRe); 829 // reference the current one. 830 RosterSpeedProfile rosterSpeedProfile = re.getSpeedProfile(); 831 //copy across the profile data 832 for (Integer i : rosterSpeedProfile.getProfileSpeeds().keySet()) { 833 tmpRsp.setSpeed(i, rosterSpeedProfile.getProfileSpeeds().get(i).getForwardSpeed(), rosterSpeedProfile.getProfileSpeeds().get(i).getReverseSpeed()); 834 } 835 //copy, merge the newdata speed points 836 for (Map.Entry<Integer, SpeedStep> entry : speeds.entrySet()) { 837 tmpRsp.setSpeed(entry.getKey(), entry.getValue().getForwardSpeed(), entry.getValue().getReverseSpeed()); 838 } 839 // show, its a bit convoluted, to get the speed table 840 // we have to set the new profile in the tmp rosterentry 841 // and ask for it back as a speedtable. 842 tmpRe.setSpeedProfile(tmpRsp); 843 RosterSpeedProfile tmpSp = tmpRe.getSpeedProfile(); 844 if (tmpSp != null) { 845 if (table != null) { 846 table.dispose(); 847 } 848 table = new SpeedProfileTable(tmpSp, tmpRe.getId()); 849 table.setVisible(true); 850 return; 851 } 852 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoSpeedProfile")); 853 setButtonStates(true); 854 } 855 856 void clearNewData() { 857 speeds.clear(); 858 } 859 860 void removeSpeedProfile() { 861 cancelButton(); 862 RosterSpeedProfile rosterSpeedProfile = re.getSpeedProfile(); 863 if (rosterSpeedProfile != null) { 864 rosterSpeedProfile.clearCurrentProfile(); 865 } 866 re.updateFile(); 867 Roster.getDefault().writeRoster(); 868 save = false; 869 } 870 871 /** 872 * View the new data collected we create a dummy entry and file with 873 * collected data 874 */ 875 void viewNewProfileData() { 876 RosterEntry tmpRe = new RosterEntry(); 877 RosterSpeedProfile rosterSpeedProfile = tmpRe.getSpeedProfile(); 878 if (rosterSpeedProfile == null) { 879 rosterSpeedProfile = new RosterSpeedProfile(tmpRe); 880 tmpRe.setSpeedProfile(rosterSpeedProfile); 881 } 882 for (Map.Entry<Integer, SpeedStep> entry : speeds.entrySet()) { 883 rosterSpeedProfile.setSpeed(entry.getKey(), entry.getValue().getForwardSpeed(), entry.getValue().getReverseSpeed()); 884 } 885 886 RosterSpeedProfile speedProfile = tmpRe.getSpeedProfile(); 887 if (speedProfile != null) { 888 if (table != null) { 889 table.dispose(); 890 } 891 table = new SpeedProfileTable(speedProfile, tmpRe.getId()); 892 table.setVisible(true); 893 return; 894 } 895 896 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoSpeedProfile")); 897 setButtonStates(true); 898 } 899 900 /** 901 * View the current speedprofile table entrys 902 */ 903 void viewRosterProfileData() { 904 if (reBox.getSelectedRosterEntries().length == 0) { 905 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoRosterSelected")); 906 setButtonStates(true); 907 return; 908 } 909 re = reBox.getSelectedRosterEntries()[0]; 910 if (re != null) { 911 RosterSpeedProfile speedProfile = re.getSpeedProfile(); 912 if (speedProfile != null) { 913 if (table != null) { 914 table.dispose(); 915 } 916 table = new SpeedProfileTable(re.getSpeedProfile(), re.getId()); 917 table.setVisible(true); 918 return; 919 } 920 } 921 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoSpeedProfile")); 922 setButtonStates(true); 923 } 924 925 /** 926 * If we have a throttle, set speed zero and release 927 */ 928 private void releaseThrottle() { 929 if (t != null) { 930 log.debug("t not null"); 931 t.setSpeedSetting(0.0f); 932 try { 933 Thread.sleep(250); 934 } catch (InterruptedException e) { 935 log.warn("Wait interupted, release throttle immediatlely"); 936 } 937 log.debug("releaseing[{}]", t.getLocoAddress().getNumber()); 938 InstanceManager.throttleManagerInstance().releaseThrottle(t, this); 939 t = null; 940 } 941 } 942 943 /** 944 * We are canceling, release throttle, reset sensors. 945 */ 946 947 void cancelButton() { 948 releaseThrottle(); 949 if (t != null) { 950 t.setSpeedSetting(0.0f); 951 try { 952 Thread.sleep(250); 953 } catch (InterruptedException e) { 954 // Nothing I can do. 955 } 956 957 InstanceManager.throttleManagerInstance().releaseThrottle(t, this); 958 t = null; 959 } 960 if (startSensor != null) { 961 startSensor.removePropertyChangeListener(startListener); 962 } 963 if (finishSensor != null) { 964 finishSensor.removePropertyChangeListener(finishListener); 965 } 966 if (middleListener != null) { 967 middleBlockSensor.getSensor().removePropertyChangeListener(middleListener); 968 } 969 setButtonStates(true); 970 } 971 972 void testButton() { 973 // TODO Should also test that the step is no greater than those available on the throttle. 974 try { 975 Integer.parseInt(speedStepTest.getText()); 976 } catch (NumberFormatException e) { 977 JmriJOptionPane.showMessageDialog(this, 978 Bundle.getMessage("ErrorSpeedStep", Bundle.getMessage("LabelTestStep"))); 979 return; 980 } 981 setupProfile(); 982 983 } 984 985 void stopTrainTest() { 986 int sectionlength = Integer.parseInt(lengthField.getText()); 987 re.getSpeedProfile().changeLocoSpeed(t, sectionlength, 0.0f); 988 setButtonStates(true); 989 startSensor.removePropertyChangeListener(startListener); 990 } 991 992 long startTime; 993 long finishTime; 994 995 ArrayList<Double> forwardOverRuns = new ArrayList<>(); 996 ArrayList<Double> reverseOverRuns = new ArrayList<>(); 997 998 JPanel update; 999 1000 static class SensorDetails { 1001 1002 Sensor sensor = null; 1003 long inactiveDelay = 0; 1004 long activeDelay = 0; 1005 boolean usingGlobal = false; 1006 1007 SensorDetails(Sensor sen) { 1008 sensor = sen; 1009 usingGlobal = sen.getUseDefaultTimerSettings(); 1010 activeDelay = sen.getSensorDebounceGoingActiveTimer(); 1011 inactiveDelay = sen.getSensorDebounceGoingInActiveTimer(); 1012 } 1013 1014 void setupSensor() { 1015 sensor.setUseDefaultTimerSettings(false); 1016 sensor.setSensorDebounceGoingActiveTimer(0); 1017 sensor.setSensorDebounceGoingInActiveTimer(0); 1018 } 1019 1020 void resetDetails() { 1021 sensor.setUseDefaultTimerSettings(usingGlobal); 1022 sensor.setSensorDebounceGoingActiveTimer(activeDelay); 1023 sensor.setSensorDebounceGoingInActiveTimer(inactiveDelay); 1024 } 1025 1026 Sensor getSensor() { 1027 return sensor; 1028 } 1029 1030 } 1031 1032 TreeMap<Integer, SpeedStep> speeds = new TreeMap<>(); 1033 1034 static class SpeedStep { 1035 1036 float forward = 0.0f; 1037 float reverse = 0.0f; 1038 1039 SpeedStep() { 1040 } 1041 1042 void setForwardSpeed(float speed) { 1043 forward = speed; 1044 } 1045 1046 void setReverseSpeed(float speed) { 1047 reverse = speed; 1048 } 1049 1050 float getForwardSpeed() { 1051 return forward; 1052 } 1053 1054 float getReverseSpeed() { 1055 return reverse; 1056 } 1057 } 1058 1059 /* 1060 * Start of code for saving and restoring the settings 1061 */ 1062 1063 /** 1064 * Save current sensor and block information to file 1065 */ 1066 private void doSaveSettings() { 1067 log.debug("Start storing SpeedProfiler settings..."); 1068 1069 // Create root element 1070 Element root = new Element(XML_ROOT, XML_NAMESPACE); 1071 1072 Element values; 1073 1074 // Store configuration 1075 root.addContent(values = new Element("configuration")); 1076 if (lengthField.getText().length() > 0) { 1077 values.addContent(new Element("length").addContent(lengthField.getText())); 1078 } 1079 if (sensorDelay.getText().length() > 0) { 1080 values.addContent(new Element("sensordelay").addContent(sensorDelay.getText())); 1081 } 1082 // Store values 1083 //if (sensorAPanel.getNamedBean(). > 0) { 1084 // Create sensors element 1085 root.addContent(values = new Element("sensors")); 1086 1087 // Store start sensor 1088 Element e = new Element("sensor"); 1089 e.addContent(new Element("sensorname").addContent("sensorAPanel")); 1090 e.addContent(new Element("sensorvalue").addContent(sensorAPanel.getDisplayName())); 1091 values.addContent(e); 1092 e = new Element("sensor"); 1093 e.addContent(new Element("sensorname").addContent("sensorBPanel")); 1094 e.addContent(new Element("sensorvalue").addContent(sensorBPanel.getDisplayName())); 1095 values.addContent(e); 1096 e = new Element("sensor"); 1097 e.addContent(new Element("sensorname").addContent("sensorCPanel")); 1098 e.addContent(new Element("sensorvalue").addContent(sensorCPanel.getDisplayName())); 1099 values.addContent(e); 1100 root.addContent(values = new Element("steps")); 1101 if (speedStepFrom.getText().length() > 0) { 1102 values.addContent(new Element("speedStepFrom").addContent(speedStepFrom.getText())); 1103 } 1104 if (speedStepTo.getText().length() > 0) { 1105 values.addContent(new Element("speedStepTo").addContent(speedStepTo.getText())); 1106 } 1107 if (speedStepIncr.getText().length() > 0) { 1108 values.addContent(new Element("speedStepIncr").addContent(speedStepIncr.getText())); 1109 } 1110 1111 try { 1112 ProfileUtils.getAuxiliaryConfiguration(ProfileManager.getDefault().getActiveProfile()) 1113 .putConfigurationFragment(JDOMUtil.toW3CElement(root), true); 1114 } catch (JDOMException ex) { 1115 log.error("Unable to create create XML", ex); 1116 } 1117 1118 log.debug("...done"); 1119 } 1120 1121 /** 1122 * Load the Block and sensor information previously saved. 1123 */ 1124 private void doLoad() { 1125 Element root; 1126 1127 log.debug("Check if there's anything to load"); 1128 try { 1129 root = JDOMUtil.toJDOMElement(ProfileUtils.getAuxiliaryConfiguration(ProfileManager.getDefault().getActiveProfile()) 1130 .getConfigurationFragment(XML_ROOT, XML_NAMESPACE, true)); 1131 } catch (NullPointerException ex) { 1132 // expected if never saved before 1133 log.debug("Nothing to load"); 1134 return; 1135 } 1136 1137 log.debug("Start loading SpeedProfiler settings..."); 1138 1139 // First read configuration 1140 if (root.getChild("configuration") != null) { 1141 List<Element> l = root.getChild("configuration").getChildren(); 1142 if (log.isDebugEnabled()) { 1143 log.debug("readFile sees {} configurations", l.size()); 1144 } 1145 for (int i = 0; i < l.size(); i++) { 1146 Element e = l.get(i); 1147 switch (e.getName()) { 1148 case "length": 1149 lengthField.setText(e.getValue()); 1150 break; 1151 case "sensordelay": 1152 sensorDelay.setText(e.getValue()); 1153 break; 1154 default: 1155 log.warn("Invalid field in PanelProSpeedProfiler.xml"); 1156 } 1157 } 1158 } 1159 // Now read sensor information 1160 if (root.getChild("sensors") != null) { 1161 List<Element> l = root.getChild("sensors").getChildren("sensor"); 1162 if (log.isDebugEnabled()) { 1163 log.debug("readFile sees {} sensors", l.size()); 1164 } 1165 SensorManager manager = InstanceManager.getDefault(SensorManager.class); 1166 for (int i = 0; i < l.size(); i++) { 1167 Element e = l.get(i); 1168 String sensorType = e.getChild("sensorname").getText(); 1169 switch (sensorType) { 1170 case "sensorAPanel": 1171 sensorAPanel.setDefaultNamedBean(manager.getByUserName(e.getChild("sensorvalue").getText())); 1172 break; 1173 case "sensorBPanel": 1174 sensorBPanel.setDefaultNamedBean(manager.getByUserName(e.getChild("sensorvalue").getText())); 1175 break; 1176 case "sensorCPanel": 1177 sensorCPanel.setDefaultNamedBean(manager.getByUserName(e.getChild("sensorvalue").getText())); 1178 break; 1179 default: 1180 log.warn("Invalid Sensor found in DecoderProSpeedProfile.xml"); 1181 } 1182 } 1183 } 1184 if (root.getChild("steps") != null) { 1185 List<Element> l = root.getChild("steps").getChildren(); 1186 for (int i = 0; i < l.size(); i++) { 1187 Element e = l.get(i); 1188 switch (e.getName()) { 1189 case "speedStepFrom": 1190 speedStepFrom.setText(e.getValue()); 1191 break; 1192 case "speedStepTo": 1193 speedStepTo.setText(e.getValue()); 1194 break; 1195 case "speedStepIncr": 1196 speedStepIncr.setText(e.getValue()); 1197 break; 1198 default: 1199 log.warn("Invalid field in steps of PanelProSpeedProfiler.xml"); 1200 } 1201 } 1202 } 1203 1204 log.debug("...done"); 1205 } 1206 1207 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SpeedProfilePanel.class); 1208 1209}