001package jmri.jmrix.bachrus.speedmatcher.basic;
002
003import jmri.DccThrottle;
004import jmri.jmrix.bachrus.Speed;
005
006/**
007 * This is a simple speed matcher which will speed match a locomotive to a given
008 * start and top speed using the complex speed table. Speed steps 1, 10, 19, and
009 * 28 will be set according to values interpolated linearly between the given
010 * start and to speeds. Values for the remaining CVs will interpolated between
011 * these 4 CVs. This is done to reduce the time the speed match takes and to
012 * increase likelihood of success.
013 *
014 * @author Todd Wegter Copyright (C) 2024
015 */
016public class BasicSpeedTableSpeedMatcher extends BasicSpeedMatcher {
017
018    //<editor-fold defaultstate="collapsed" desc="Constants">
019    private final int INITIAL_STEP1 = 1;
020    private final int INITIAL_STEP28 = 255;
021    private final int INITIAL_TRIM = 128;
022
023    private final int STEP28_MAX = 255;
024    private final int STEP28_MIN = 28;
025    private final int STEP19_MIN = 19;
026    private final int STEP10_MIN = 10;
027    private final int STEP1_MIN = 1;
028    //</editor-fold>
029
030    //<editor-fold defaultstate="collapsed" desc="Enums">
031    protected enum SpeedMatcherState {
032        IDLE,
033        WAIT_FOR_THROTTLE,
034        INIT_THROTTLE,
035        INIT_ACCEL,
036        INIT_DECEL,
037        INIT_SPEED_TABLE,
038        INIT_FORWARD_TRIM,
039        INIT_REVERSE_TRIM,
040        POST_INIT,
041        FORWARD_WARM_UP,
042        FORWARD_SPEED_MATCH_STEP28,
043        RE_INIT_SPEED_TABLE_TOP_THIRD,
044        FORWARD_SPEED_MATCH_STEP19,
045        RE_INIT_SPEED_TABLE_MIDDLE_THIRD,
046        FORWARD_SPEED_MATCH_STEP10,
047        RE_INIT_SPEED_TABLE_BOTTOM_THIRD,
048        FORWARD_SPEED_MATCH_STEP1,
049        INTERPOLATE_SPEED_TABLE,
050        POST_INTERPOLATE,
051        REVERSE_WARM_UP,
052        REVERSE_SPEED_MATCH_TRIM,
053        COMPLETE,
054        USER_STOPPED,
055        CLEAN_UP,
056    }
057    //</editor-fold>
058
059    //<editor-fold defaultstate="collapsed" desc="Instance Variables">
060    private SpeedTableStep initSpeedTableStep;
061    private int initSpeedTableStepValue;
062
063    private SpeedTableStep interpolationSpeedTableStep;
064
065    private int speedMatchCVValue = INITIAL_STEP28;
066    private int lastSpeedMatchCVValue = INITIAL_STEP28;
067
068    private int reverseTrimValue = INITIAL_TRIM;
069    private int lastReverseTrimValue = INITIAL_TRIM;
070
071    private final float targetStep28SpeedKPH;
072    private final float targetStep19SpeedKPH;
073    private final float targetStep10SpeedKPH;
074    private final float targetStep1SpeedKPH;
075
076    private int step28CVValue;
077    private int step19CVValue;
078    private int step10CVValue;
079    private int step1CVValue;
080
081    private SpeedMatcherState speedMatcherState = SpeedMatcherState.IDLE;
082    //</editor-fold>
083
084    /**
085     * Constructs the BasicSpeedTableSpeedMatcher from a BasicSpeedMatcherConfig
086     *
087     * @param config SpeedStepScaleSpeedMatcherConfig
088     */
089    public BasicSpeedTableSpeedMatcher(BasicSpeedMatcherConfig config) {
090        super(config);
091
092        targetStep28SpeedKPH = targetTopSpeedKPH;
093        targetStep19SpeedKPH = getSpeedForSpeedStep(SpeedTableStep.STEP19, targetStartSpeedKPH, targetTopSpeedKPH);
094        targetStep10SpeedKPH = getSpeedForSpeedStep(SpeedTableStep.STEP10, targetStartSpeedKPH, targetTopSpeedKPH);
095        targetStep1SpeedKPH = targetStartSpeedKPH;
096    }
097
098    //<editor-fold defaultstate="collapsed" desc="SpeedMatcher Overrides">
099    /**
100     * Starts the speed matching process
101     *
102     * @return true if speed matching started successfully, false otherwise
103     */
104    @Override
105    public boolean startSpeedMatcher() {
106        if (!validate()) {
107            return false;
108        }
109
110        //reset instance variables
111        speedMatchCVValue = INITIAL_STEP28;
112        lastSpeedMatchCVValue = INITIAL_STEP28;
113        reverseTrimValue = INITIAL_TRIM;
114        lastReverseTrimValue = INITIAL_TRIM;
115
116        speedMatcherState = SpeedMatcherState.WAIT_FOR_THROTTLE;
117
118        if (!initializeAndStartSpeedMatcher(e -> speedMatchTimeout())) {
119            cleanUpSpeedMatcher();
120            return false;
121        }
122
123        startStopButton.setText(Bundle.getMessage("SpeedMatchStopBtn"));
124
125        return true;
126    }
127
128    /**
129     * Stops the speed matching process
130     */
131    @Override
132    public void stopSpeedMatcher() {
133        if (!isSpeedMatcherIdle()) {
134            logger.info("Speed matching manually stopped");
135            userStop();
136        } else {
137            cleanUpSpeedMatcher();
138        }
139    }
140
141    /**
142     * Indicates if the speed matcher is idle (not currently speed matching)
143     *
144     * @return true if idle, false otherwise
145     */
146    @Override
147    public boolean isSpeedMatcherIdle() {
148        return speedMatcherState == SpeedMatcherState.IDLE;
149    }
150
151    /**
152     * Cleans up the speed matcher when speed matching is stopped or is finished
153     */
154    @Override
155    protected void cleanUpSpeedMatcher() {
156        speedMatcherState = SpeedMatcherState.IDLE;
157        super.cleanUpSpeedMatcher();
158    }
159    //</editor-fold>
160
161    //<editor-fold defaultstate="collapsed" desc="Speed Matcher State">
162    /**
163     * Main speed matching timeout handler. This is the state machine that
164     * effectively does the speed matching process.
165     */
166    private synchronized void speedMatchTimeout() {
167        switch (speedMatcherState) {
168            case WAIT_FOR_THROTTLE:
169                cleanUpSpeedMatcher();
170                logger.error("Timeout waiting for throttle");
171                statusLabel.setText(Bundle.getMessage("StatusTimeout"));
172                break;
173
174            case INIT_THROTTLE:
175                //set throttle to 0 for init
176                setThrottle(true, 0);
177                initNextSpeedMatcherState(SpeedMatcherState.INIT_ACCEL);
178                break;
179
180            case INIT_ACCEL:
181                //set acceleration momentum to 0 (CV 3)
182                if (programmerState == ProgrammerState.IDLE) {
183                    writeMomentumAccel(INITIAL_MOMENTUM);
184                    initNextSpeedMatcherState(SpeedMatcherState.INIT_DECEL);
185                }
186                break;
187
188            case INIT_DECEL:
189                //set deceleration mementum to 0 (CV 4)
190                if (programmerState == ProgrammerState.IDLE) {
191                    writeMomentumDecel(INITIAL_MOMENTUM);
192                    initNextSpeedMatcherState(SpeedMatcherState.INIT_SPEED_TABLE);
193                }
194                break;
195
196            case INIT_SPEED_TABLE:
197                //initialize every speed table step to 1 except speed table step 28 = 255
198                if (programmerState == ProgrammerState.IDLE) {
199                    if (stepDuration == 0) {
200                        initSpeedTableStepValue = INITIAL_STEP1;
201                        initSpeedTableStep = SpeedTableStep.STEP1;
202                        stepDuration = 1;
203                    }
204
205                    if (initSpeedTableStep == SpeedTableStep.STEP28) {
206                        initSpeedTableStepValue = INITIAL_STEP28;
207                    }
208
209                    writeSpeedTableStep(initSpeedTableStep, initSpeedTableStepValue);
210
211                    initSpeedTableStep = initSpeedTableStep.getNext();
212                    if (initSpeedTableStep == null) {
213                        initNextSpeedMatcherState(SpeedMatcherState.INIT_FORWARD_TRIM);
214                    }
215                }
216                break;
217
218            case INIT_FORWARD_TRIM:
219                //set forward trim to 128 (CV 66)
220                if (programmerState == ProgrammerState.IDLE) {
221                    writeForwardTrim(INITIAL_TRIM);
222                    initNextSpeedMatcherState(SpeedMatcherState.INIT_REVERSE_TRIM);
223                }
224                break;
225
226            case INIT_REVERSE_TRIM:
227                //set reverse trim to 128 (CV 95)
228                if (programmerState == ProgrammerState.IDLE) {
229                    writeReverseTrim(INITIAL_TRIM);
230                    initNextSpeedMatcherState(SpeedMatcherState.POST_INIT);
231                }
232                break;
233
234            case POST_INIT: {
235                statusLabel.setText(Bundle.getMessage("StatRestoreThrottle"));
236
237                //un-brick Digitrax decoders
238                setThrottle(false, 0);
239                setThrottle(true, 0);
240
241                SpeedMatcherState nextState;
242                if (warmUpForwardSeconds > 0) {
243                    nextState = SpeedMatcherState.FORWARD_WARM_UP;
244                } else {
245                    nextState = SpeedMatcherState.FORWARD_SPEED_MATCH_STEP28;
246                }
247                initNextSpeedMatcherState(nextState);
248                break;
249            }
250
251            case FORWARD_WARM_UP:
252                //Run 4 minutes at high speed forward
253                statusLabel.setText(Bundle.getMessage("StatForwardWarmUp", warmUpForwardSeconds - stepDuration));
254
255                if (stepDuration >= warmUpForwardSeconds) {
256                    initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP28);
257                } else {
258                    if (stepDuration == 0) {
259                        setSpeedMatchStateTimerDuration(5000);
260                        setThrottle(true, 28);
261                    }
262                    stepDuration += 5;
263                }
264                break;
265
266            case FORWARD_SPEED_MATCH_STEP28:
267                //Use PID Controller to adjust Speed Step 28 to the max speed
268                if (programmerState == ProgrammerState.IDLE) {
269                    speedMatchSpeedStepInner(SpeedTableStep.STEP28, targetStep28SpeedKPH, STEP28_MAX, STEP28_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_TOP_THIRD);
270                    step28CVValue = speedMatchCVValue;
271                }
272                break;
273
274            case RE_INIT_SPEED_TABLE_TOP_THIRD:
275                //Re-initialize Speed Steps 19-27 based off value for Step 28
276                if (programmerState == ProgrammerState.IDLE) {
277                    if (stepDuration == 0) {
278                        initSpeedTableStep = SpeedTableStep.STEP27;
279                        stepDuration = 1;
280                    }
281
282                    writeSpeedTableStep(initSpeedTableStep, step28CVValue);
283
284                    if (initSpeedTableStep == SpeedTableStep.STEP19) {
285                        initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP19);
286                    } else {
287                        initSpeedTableStep = initSpeedTableStep.getPrevious();
288                    }
289                }
290                break;
291
292            case FORWARD_SPEED_MATCH_STEP19:
293                //Use PID Controller to adjust Speed Step 19 to the interpolated speed
294                if (programmerState == ProgrammerState.IDLE) {
295                    speedMatchSpeedStepInner(SpeedTableStep.STEP19, targetStep19SpeedKPH, step28CVValue - 9, STEP19_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_MIDDLE_THIRD);
296                    step19CVValue = speedMatchCVValue;
297                }
298                break;
299
300            case RE_INIT_SPEED_TABLE_MIDDLE_THIRD:
301                //Re-initialize Speed Steps 10-18 based off value for Step 19
302                if (programmerState == ProgrammerState.IDLE) {
303                    if (stepDuration == 0) {
304                        initSpeedTableStep = SpeedTableStep.STEP18;
305                        stepDuration = 1;
306                    }
307
308                    writeSpeedTableStep(initSpeedTableStep, step19CVValue);
309
310                    if (initSpeedTableStep == SpeedTableStep.STEP10) {
311                        initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP10);
312                    } else {
313                        initSpeedTableStep = initSpeedTableStep.getPrevious();
314                    }
315
316                }
317                break;
318
319            case FORWARD_SPEED_MATCH_STEP10:
320                //Use PID Controller to adjust Speed Step 10 to the interpolated speed
321                if (programmerState == ProgrammerState.IDLE) {
322                    speedMatchSpeedStepInner(SpeedTableStep.STEP10, targetStep10SpeedKPH, step19CVValue - 9, STEP10_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_BOTTOM_THIRD);
323                    step10CVValue = speedMatchCVValue;
324                }
325                break;
326
327            case RE_INIT_SPEED_TABLE_BOTTOM_THIRD:
328                //Re-initialize Speed Steps 1-9 based off value for Step 10
329                if (programmerState == ProgrammerState.IDLE) {
330                    if (stepDuration == 0) {
331                        initSpeedTableStep = SpeedTableStep.STEP9;
332                        stepDuration = 1;
333                    }
334
335                    writeSpeedTableStep(initSpeedTableStep, step10CVValue);
336
337                    if (initSpeedTableStep == SpeedTableStep.STEP1) {
338                        initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP1);
339                    } else {
340                        initSpeedTableStep = initSpeedTableStep.getPrevious();
341                    }
342
343                }
344                break;
345
346            case FORWARD_SPEED_MATCH_STEP1:
347                //Use PID Controller to adjust Speed Step 1 to the minimum speed
348                if (programmerState == ProgrammerState.IDLE) {
349                    speedMatchSpeedStepInner(SpeedTableStep.STEP1, targetStep1SpeedKPH, step10CVValue - 9, STEP1_MIN, SpeedMatcherState.INTERPOLATE_SPEED_TABLE);
350                    step1CVValue = speedMatchCVValue;
351                }
352                break;
353
354            case INTERPOLATE_SPEED_TABLE: {
355                //Interpolate the values of the intermediate speed steps
356                if (programmerState == ProgrammerState.IDLE) {
357                    if (stepDuration == 0) {
358                        setThrottle(true, 0);
359                        interpolationSpeedTableStep = SpeedTableStep.STEP27;
360                        stepDuration = 1;
361                    }
362
363                    int interpolatedSpeedStepCVValue = getInterpolatedSpeedTableCVValue(interpolationSpeedTableStep);
364                    writeSpeedTableStep(interpolationSpeedTableStep, interpolatedSpeedStepCVValue);
365
366                    do {
367                        interpolationSpeedTableStep = interpolationSpeedTableStep.getPrevious();
368                    } while (interpolationSpeedTableStep == SpeedTableStep.STEP19 || interpolationSpeedTableStep == SpeedTableStep.STEP10);
369
370                    if (interpolationSpeedTableStep == SpeedTableStep.STEP1) {
371                        initNextSpeedMatcherState(SpeedMatcherState.POST_INTERPOLATE);
372                    }
373
374                }
375                break;
376            }
377
378            case POST_INTERPOLATE: {
379                statusLabel.setText(Bundle.getMessage("StatRestoreThrottle"));
380
381                //un-brick Digitrax decoders
382                setThrottle(false, 0);
383                setThrottle(true, 0);
384
385                SpeedMatcherState nextState;
386                if (trimReverseSpeed) {
387                    if (warmUpReverseSeconds > 0) {
388                        nextState = SpeedMatcherState.REVERSE_WARM_UP;
389                    } else {
390                        nextState = SpeedMatcherState.REVERSE_SPEED_MATCH_TRIM;
391                    }
392                } else {
393                    nextState = SpeedMatcherState.COMPLETE;
394                }
395                initNextSpeedMatcherState(nextState);
396                break;
397            }
398
399            case REVERSE_WARM_UP:
400                //Run specified reverse warm up time at high speed in reverse
401                statusLabel.setText(Bundle.getMessage("StatReverseWarmUp", warmUpReverseSeconds - stepDuration));
402
403                if (stepDuration >= warmUpReverseSeconds) {
404                    initNextSpeedMatcherState(SpeedMatcherState.REVERSE_SPEED_MATCH_TRIM);
405                } else {
406                    if (stepDuration == 0) {
407                        setSpeedMatchStateTimerDuration(5000);
408                        setThrottle(false, 28);
409                    }
410                    stepDuration += 5;
411                }
412
413                break;
414
415            case REVERSE_SPEED_MATCH_TRIM:
416                //Use PID controller logic to adjust reverse trim until high speed reverse speed matches forward
417                if (programmerState == ProgrammerState.IDLE) {
418                    if (stepDuration == 0) {
419                        statusLabel.setText(Bundle.getMessage("StatSettingReverseTrim"));
420                        setThrottle(false, 28);
421                        setSpeedMatchStateTimerDuration(8000);
422                        stepDuration = 1;
423                    } else {
424                        setSpeedMatchError(targetTopSpeedKPH);
425
426                        if (Math.abs(speedMatchError) < ALLOWED_SPEED_MATCH_ERROR) {
427                            initNextSpeedMatcherState(SpeedMatcherState.COMPLETE);
428                        } else {
429                            reverseTrimValue = getNextSpeedMatchValue(lastReverseTrimValue, REVERSE_TRIM_MAX, REVERSE_TRIM_MIN);
430
431                            if (((lastReverseTrimValue == REVERSE_TRIM_MAX) || (lastReverseTrimValue == REVERSE_TRIM_MIN)) && (reverseTrimValue == lastReverseTrimValue)) {
432                                statusLabel.setText(Bundle.getMessage("StatSetReverseTrimFail"));
433                                logger.info("Unable to trim reverse to match forward");
434                                abort();
435                                break;
436                            }
437
438                            lastReverseTrimValue = reverseTrimValue;
439                            writeReverseTrim(reverseTrimValue);
440                        }
441                    }
442                }
443                break;
444
445            case COMPLETE:
446                if (programmerState == ProgrammerState.IDLE) {
447                    statusLabel.setText(Bundle.getMessage("StatSpeedMatchComplete"));
448                    setThrottle(true, 0);
449                    initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP);
450                }
451                break;
452
453            case USER_STOPPED:
454                if (programmerState == ProgrammerState.IDLE) {
455                    statusLabel.setText(Bundle.getMessage("StatUserStoppedSpeedMatch"));
456                    setThrottle(true, 0);
457                    initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP);
458                }
459                break;
460
461            case CLEAN_UP:
462                //wrap it up
463                if (programmerState == ProgrammerState.IDLE) {
464                    cleanUpSpeedMatcher();
465                }
466                break;
467
468            default:
469                cleanUpSpeedMatcher();
470                logger.error("Unexpected speed match timeout");
471                break;
472        }
473
474        if (speedMatcherState != SpeedMatcherState.IDLE) {
475            startSpeedMatchStateTimer();
476        }
477    }
478    //</editor-fold>
479
480    //<editor-fold defaultstate="collapsed" desc="ThrottleListener Overrides">
481    /**
482     * Called when a throttle is found
483     *
484     * @param t the requested DccThrottle
485     */
486    @Override
487    public void notifyThrottleFound(DccThrottle t) {
488        super.notifyThrottleFound(t);
489
490        if (speedMatcherState == SpeedMatcherState.WAIT_FOR_THROTTLE) {
491            logger.info("Starting speed matching");
492            // using speed matching timer to trigger each phase of speed matching            
493            initNextSpeedMatcherState(SpeedMatcherState.INIT_THROTTLE);
494            startSpeedMatchStateTimer();
495        } else {
496            cleanUpSpeedMatcher();
497        }
498    }
499    //</editor-fold>
500
501    //<editor-fold defaultstate="collapsed" desc="Helper Functions">
502    /**
503     * Gets the interpolated CV value for the given speed step in the speed
504     * table
505     *
506     * @param speedStep the SpeedTableStep to get the speed for
507     * @return the target speed for the given speed step in KPH
508     */
509    private int getInterpolatedSpeedTableCVValue(SpeedTableStep speedStep) {
510        SpeedTableStep maxStep;
511        SpeedTableStep minStep;
512        int maxStepCVValue;
513        int minStepCVValue;
514
515        if (speedStep.getSpeedStep() >= SpeedTableStep.STEP19.getSpeedStep()) {
516            maxStep = SpeedTableStep.STEP28;
517            minStep = SpeedTableStep.STEP19;
518            maxStepCVValue = step28CVValue;
519            minStepCVValue = step19CVValue;
520        } else if (speedStep.getSpeedStep() >= SpeedTableStep.STEP10.getSpeedStep()) {
521            maxStep = SpeedTableStep.STEP19;
522            minStep = SpeedTableStep.STEP10;
523            maxStepCVValue = step19CVValue;
524            minStepCVValue = step10CVValue;
525        } else {
526            maxStep = SpeedTableStep.STEP10;
527            minStep = SpeedTableStep.STEP1;
528            maxStepCVValue = step10CVValue;
529            minStepCVValue = step1CVValue;
530        }
531
532        return Math.round(minStepCVValue + ((((float) (maxStepCVValue - minStepCVValue)) / (maxStep.getSpeedStep() - minStep.getSpeedStep())) * (speedStep.getSpeedStep() - minStep.getSpeedStep())));
533    }
534
535    /**
536     * Helper function for speed matching a given speed step
537     *
538     * @param speedStep      the SpeedTableStep to speed match
539     * @param targetSpeedKPH the target speed in KPH
540     * @param maxCVValue     the maximum allowable value for the CV
541     * @param minCVValue     the minimum allowable value for the CV
542     * @param nextState      the SpeedMatcherState to advance to if speed
543     *                       matching is complete
544     */
545    private void speedMatchSpeedStepInner(SpeedTableStep speedStep, float targetSpeedKPH, int maxCVValue, int minCVValue, SpeedMatcherState nextState) {
546        if (stepDuration == 0) {
547            statusLabel.setText(Bundle.getMessage("StatSettingSpeed", speedStep.getCV() + " (Speed Step " + String.valueOf(speedStep.getSpeedStep()) + ")"));
548            logger.info("Setting CV {} (speed step {}) to {} KPH ({} MPH)", speedStep.getCV(), speedStep.getSpeedStep(), String.valueOf(targetSpeedKPH), String.valueOf(Speed.kphToMph(targetSpeedKPH)));
549            setThrottle(true, speedStep.getSpeedStep());
550            setSpeedMatchStateTimerDuration(8000);
551            stepDuration = 1;
552        } else {
553            setSpeedMatchError(targetSpeedKPH);
554
555            if (Math.abs(speedMatchError) < ALLOWED_SPEED_MATCH_ERROR) {
556                initNextSpeedMatcherState(nextState);
557            } else {
558                speedMatchCVValue = getNextSpeedMatchValue(lastSpeedMatchCVValue, maxCVValue, minCVValue);
559
560                if (((speedMatchCVValue == maxCVValue) || (speedMatchCVValue == minCVValue)) && (speedMatchCVValue == lastSpeedMatchCVValue)) {
561                    statusLabel.setText(Bundle.getMessage("StatSetSpeedFail", speedStep.getCV() + " (Speed Step " + String.valueOf(speedStep.getSpeedStep()) + ")"));
562                    logger.info("Unable to achieve desired speed for CV {} (Speed Step {})", speedStep.getCV(), String.valueOf(speedStep.getSpeedStep()));
563                    abort();
564                    return;
565                }
566
567                lastSpeedMatchCVValue = speedMatchCVValue;
568                writeSpeedTableStep(speedStep, speedMatchCVValue);
569            }
570        }
571    }
572
573    /**
574     * Aborts the speed matching process programmatically
575     */
576    private void abort() {
577        initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP);
578    }
579
580    /**
581     * Stops the speed matching process due to user input
582     */
583    private void userStop() {
584        initNextSpeedMatcherState(SpeedMatcherState.USER_STOPPED);
585    }
586
587    /**
588     * Sets up the speed match state by clearing the speed match error, clearing
589     * the step duration, setting the timer duration, and setting the next state
590     *
591     * @param nextState - next SpeedMatcherState to set
592     */
593    protected void initNextSpeedMatcherState(SpeedMatcherState nextState) {
594        resetSpeedMatchError();
595        stepDuration = 0;
596        speedMatcherState = nextState;
597        setSpeedMatchStateTimerDuration(1800);
598    }
599    //</editor-fold>
600
601    //debugging logger
602    private final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BasicSpeedTableSpeedMatcher.class);
603}