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