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