001package jmri.jmrix;
002
003import com.fasterxml.jackson.databind.util.StdDateFormat;
004import java.beans.PropertyChangeListener;
005import java.util.Arrays;
006import java.util.Date;
007import java.util.List;
008import jmri.BasicRosterEntry;
009import jmri.CommandStation;
010import jmri.LocoAddress;
011import jmri.SpeedStepMode;
012import jmri.DccLocoAddress;
013import jmri.DccThrottle;
014import jmri.InstanceManager;
015import jmri.SystemConnectionMemo;
016import jmri.Throttle;
017import jmri.ThrottleListener;
018import jmri.beans.PropertyChangeSupport;
019
020/**
021 * An abstract implementation of DccThrottle. Based on Glen Oberhauser's
022 * original LnThrottleManager implementation.
023 * <p>
024 * Note that this implements DccThrottle, not Throttle directly, so it has some
025 * DCC-specific content.
026 *
027 * @author Bob Jacobsen Copyright (C) 2001, 2005
028 */
029abstract public class AbstractThrottle extends PropertyChangeSupport implements DccThrottle {
030
031    protected float speedSetting;
032    /**
033     * Question: should we set a default speed step mode so it's never zero?
034     */
035    protected SpeedStepMode speedStepMode = SpeedStepMode.UNKNOWN;
036    protected boolean isForward;
037    
038    /**
039     * Array of Function values.
040     * <p>
041     * Contains current Boolean value for functions.
042     * This array should not be accessed directly by Throttles,
043     * use setFunction / getFunction / updateFunction.
044     * Needs to be same length as FUNCTION_MOMENTARY_BOOLEAN_ARRAY.
045     */
046    private final boolean[] FUNCTION_BOOLEAN_ARRAY;
047
048    /**
049     * Array of Momentary Function values.
050     * <p>
051     * Contains current Boolean value for Momentary function settings.
052     * Needs to be same length as FUNCTION_BOOLEAN_ARRAY.
053     */
054    private final boolean[] FUNCTION_MOMENTARY_BOOLEAN_ARRAY;
055    
056    /**
057     * Is this object still usable? Set false after dispose, this variable is
058     * used to check for incorrect usage.
059     */
060    protected boolean active;
061
062    /**
063     * Create a new AbstractThrottle with Functions 0-28..
064     * <p>
065     * All function and momentary functions set to Off.
066     * @param memo System Connection.
067     */
068    public AbstractThrottle(SystemConnectionMemo memo) {
069        active = true;
070        adapterMemo = memo;
071        FUNCTION_BOOLEAN_ARRAY = new boolean[29];
072        FUNCTION_MOMENTARY_BOOLEAN_ARRAY = new boolean[29];
073    }
074    
075    /**
076     * Create a new AbstractThrottle with custom number of functions.
077     * <p>
078     * All function and momentary functions set to Off.
079     * @param memo System Connection.
080     * @param totalFunctions total number of functions available, including 0.
081     */
082    public AbstractThrottle(SystemConnectionMemo memo, int totalFunctions) {
083        active = true;
084        adapterMemo = memo;
085        FUNCTION_BOOLEAN_ARRAY = new boolean[totalFunctions];
086        FUNCTION_MOMENTARY_BOOLEAN_ARRAY = new boolean[totalFunctions];
087    }
088
089    /**
090     * System Connection.
091     */
092    protected SystemConnectionMemo adapterMemo;
093
094    /**
095     * speed - expressed as a value {@literal 0.0 -> 1.0.} Negative means
096     * emergency stop. This is an bound parameter.
097     *
098     * @return speed
099     */
100    @Override
101    public float getSpeedSetting() {
102        return speedSetting;
103    }
104
105    /**
106     * setSpeedSetting - Implementing functions should override this function,
107     * but should either make a call to super.setSpeedSetting() to notify the
108     * listeners at the end of their work, or should notify the listeners
109     * themselves.
110     */
111    @Override
112    public void setSpeedSetting(float speed) {
113        setSpeedSetting(speed, false, false);
114        record(speed);
115    }
116
117    /**
118     * setSpeedSetting - Implementations should override this method only if
119     * they normally suppress messages to the system if, as far as JMRI can
120     * tell, the new message would make no difference to the system state (eg.
121     * the speed is the same, or effectivly the same, as the existing speed).
122     * Then, the boolean options can affect this behaviour.
123     *
124     * @param speed                 the new speed
125     * @param allowDuplicates       don't suppress messages
126     * @param allowDuplicatesOnStop don't suppress messages if the new speed is
127     *                              'stop'
128     */
129    @Override
130    public void setSpeedSetting(float speed, boolean allowDuplicates, boolean allowDuplicatesOnStop) {
131        if (Math.abs(this.speedSetting - speed) > 0.0001) {
132            firePropertyChange(SPEEDSETTING, this.speedSetting, this.speedSetting = speed);
133        }
134        record(speed);
135    }
136
137    /**
138     * setSpeedSettingAgain - set the speed and don't ever supress the sending
139     * of messages to the system
140     *
141     * @param speed the new speed
142     */
143    @Override
144    public void setSpeedSettingAgain(float speed) {
145        setSpeedSetting(speed, true, true);
146    }
147
148    /**
149     * direction This is an bound parameter.
150     *
151     * @return true if locomotive is running forward
152     */
153    @Override
154    public boolean getIsForward() {
155        return isForward;
156    }
157
158    /**
159     * Implementing functions should override this function, but should either
160     * make a call to super.setIsForward() to notify the listeners, or should
161     * notify the listeners themselves.
162     *
163     * @param forward true if forward; false otherwise
164     */
165    @Override
166    public void setIsForward(boolean forward) {
167        firePropertyChange(ISFORWARD, isForward, isForward = forward);
168    }
169
170    /*
171     * functions - note that we use the naming for DCC, though that's not the
172     * implication; see also DccThrottle interface
173     */
174
175    /**
176     * {@inheritDoc}
177     */
178    @Override
179    public boolean[] getFunctions() {
180        return Arrays.copyOf(FUNCTION_BOOLEAN_ARRAY,FUNCTION_BOOLEAN_ARRAY.length);
181    }
182
183    /**
184     * {@inheritDoc}
185     */
186    @Override
187    public boolean[] getFunctionsMomentary() {
188        return Arrays.copyOf(FUNCTION_MOMENTARY_BOOLEAN_ARRAY,
189            FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length);
190    }
191    
192    /**
193     * {@inheritDoc}
194     */
195    @Override
196    public boolean getFunction(int fN) {
197        if (fN<0 || fN > FUNCTION_BOOLEAN_ARRAY.length-1){
198            log.warn("Unhandled get function: {}",fN);
199            return false;
200        }
201        return FUNCTION_BOOLEAN_ARRAY[fN];
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public boolean getFunctionMomentary(int fN) {
209        if (fN<0 || fN > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1){
210            log.warn("Unhandled get momentary function: {}",fN);
211            return false;
212        }
213        return FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fN];
214    
215    }
216    
217    /**
218     * Notify listeners that a Throttle has disconnected and is no longer
219     * available for use.
220     * <p>
221     * For when throttles have been stolen or encounter hardware error, and a
222     * normal release / dispose is not possible.
223     */
224    protected void notifyThrottleDisconnect() {
225        firePropertyChange("ThrottleConnected", true, false); // NOI18N
226    }
227
228    // set initial values purely for changelistener following
229    // the 1st true or false will always get sent
230    private Boolean _dispatchEnabled = null;
231    private Boolean _releaseEnabled = null;
232
233    /**
234     * Notify listeners that a Throttle has Dispatch enabled or disabled.
235     * <p>
236     * For systems where dispatch availability is variable.
237     * <p>
238     * Does not notify if existing value is unchanged.
239     *
240     * @param newVal true if Dispatch enabled, else false
241     *
242     */
243    @Override
244    public void notifyThrottleDispatchEnabled(boolean newVal) {
245        firePropertyChange("DispatchEnabled", _dispatchEnabled, _dispatchEnabled = newVal); // NOI18N
246    }
247
248    /**
249     * Notify listeners that a Throttle has Release enabled or disabled.
250     * <p>
251     * For systems where release availability is variable.
252     * <p>
253     * Does not notify if existing value is unchanged.
254     *
255     * @param newVal true if Release enabled, else false
256     *
257     */
258    @Override
259    public void notifyThrottleReleaseEnabled(boolean newVal) {
260        firePropertyChange("ReleaseEnabled", _releaseEnabled, _releaseEnabled = newVal); // NOI18N
261    }
262
263    /**
264     * Temporary behaviour only allowing unique PCLs.
265     * To support Throttle PCL's ( eg. WiThrottle Server ) that rely on the 
266     * previous behaviour of only allowing 1 unique PCL instance.
267     * To be removed when WiThrottle Server has been updated.
268     * {@inheritDoc}
269     */
270    @Override
271    public void addPropertyChangeListener(PropertyChangeListener l) {
272        if ( Arrays.asList(getPropertyChangeListeners()).contains(l) ){
273            log.warn("Preventing {} adding duplicate PCL",l);
274            return;
275        }
276        super.addPropertyChangeListener(l);
277    }
278    
279    /**
280     * {@inheritDoc}
281     */
282    @Override
283    public void removePropertyChangeListener(PropertyChangeListener l) {
284        log.debug("Removing property change {}", l);
285        super.removePropertyChangeListener(l);
286        log.debug("remove listeners size is {}", getPropertyChangeListeners().length);
287        if (getPropertyChangeListeners().length == 0) {
288            log.debug("No listeners so calling ThrottleManager.dispose with an empty ThrottleListener");
289            InstanceManager.throttleManagerInstance().disposeThrottle(this, new ThrottleListener() {
290                @Override
291                public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
292                }
293
294                @Override
295                public void notifyThrottleFound(DccThrottle t) {
296                }
297
298                @Override
299                public void notifyDecisionRequired(LocoAddress address, DecisionType question) {
300                }
301            });
302        }
303    }
304
305    /**
306     * Trigger the notification of all PropertyChangeListeners. Will only notify
307     * if oldValue and newValue are not equal and non-null.
308     *
309     * @param property the name of the property to send notifications for
310     * @param oldValue the old value of the property
311     * @param newValue the new value of the property
312     * @deprecated since 4.19.5; use
313     * {@link #firePropertyChange(java.lang.String, java.lang.Object, java.lang.Object)}
314     * instead
315     */
316    @Deprecated
317    protected void notifyPropertyChangeListener(String property, Object oldValue, Object newValue) {
318        firePropertyChange(property, oldValue, newValue);
319    }
320
321    /**
322     * {@inheritDoc}
323     */
324    @Override
325    @Deprecated
326    public List<PropertyChangeListener> getListeners() {
327        return Arrays.asList(getPropertyChangeListeners());
328    }
329
330    /**
331     * Call from a ThrottleListener to dispose of the throttle instance
332     *
333     * @param l the listener requesting the dispose
334     *
335     */
336    @Override
337    public void dispose(ThrottleListener l) {
338        if (!active) {
339            log.error("Dispose called when not active");
340        }
341        InstanceManager.throttleManagerInstance().disposeThrottle(this, l);
342    }
343
344    /**
345     * {@inheritDoc}
346     */
347    @Override
348    public void dispatch(ThrottleListener l) {
349        if (!active) {
350            log.warn("dispatch called when not active");
351        }
352        InstanceManager.throttleManagerInstance().dispatchThrottle(this, l);
353    }
354
355    /**
356     * {@inheritDoc}
357     */
358    @Override
359    public void release(ThrottleListener l) {
360        if (!active) {
361            log.warn("release called when not active");
362        }
363        InstanceManager.throttleManagerInstance().releaseThrottle(this, l);
364    }
365
366    /**
367     * Dispose when finished with this Throttle.
368     * Abstract Throttles normally call finishRecord(); here.
369     */
370    abstract protected void throttleDispose();
371
372    /**
373     * to handle quantized speed. Note this can change! Valued returned is
374     * always positive.
375     *
376     * @return 1 divided by the number of speed steps this DCC throttle supports
377     */
378    @Override
379    public float getSpeedIncrement() {
380        return speedStepMode.increment;
381    }
382
383    /*
384     * functions - note that we use the naming for DCC, though that's not the
385     * implication; see also DccThrottle interface
386     */
387    
388    /**
389     * Send whole (DCC) Function Group for a particular function number.
390     * @param functionNum Function Number
391     * @param momentary False to send normal function status, true to send momentary.
392     */
393    protected void sendFunctionGroup(int functionNum, boolean momentary){
394        switch (FUNCTION_GROUPS[functionNum]) {
395            case 1:
396                if (momentary) sendMomentaryFunctionGroup1(); else sendFunctionGroup1();
397                break;
398            case 2:
399                if (momentary) sendMomentaryFunctionGroup2(); else sendFunctionGroup2();
400                break;
401            case 3:
402                if (momentary) sendMomentaryFunctionGroup3(); else sendFunctionGroup3();
403                break;
404            case 4:
405                if (momentary) sendMomentaryFunctionGroup4(); else sendFunctionGroup4();
406                break;
407            case 5:
408                if (momentary) sendMomentaryFunctionGroup5(); else sendFunctionGroup5();
409                break;
410            default:
411                break;
412        }
413    }
414    
415    /**
416     * {@inheritDoc}
417     */
418    @Override
419    public void setFunction(int functionNum, boolean newState) {
420        if (functionNum < 0 || functionNum > FUNCTION_BOOLEAN_ARRAY.length-1) {
421            log.warn("Unhandled set function number: {} {}", functionNum, this.getClass().getName());
422            return;
423        }
424        boolean old = FUNCTION_BOOLEAN_ARRAY[functionNum];
425        FUNCTION_BOOLEAN_ARRAY[functionNum] = newState;
426        sendFunctionGroup(functionNum,false);
427        firePropertyChange(Throttle.getFunctionString(functionNum), old, newState);
428    }
429
430    /**
431     * Update the state of a single function. Updates function value and
432     * ChangeListener. Does not send outward message TO hardware.
433     *
434     * @param fn    Function Number 0-28
435     * @param state On - True, Off - False
436     */
437    public void updateFunction(int fn, boolean state) {
438        if (fn < 0 || fn > FUNCTION_BOOLEAN_ARRAY.length-1) {
439            log.warn("Unhandled update function number: {} {}", fn, this.getClass().getName());
440            return;
441        }
442        boolean old = FUNCTION_BOOLEAN_ARRAY[fn];
443        FUNCTION_BOOLEAN_ARRAY[fn] = state;
444        firePropertyChange(Throttle.getFunctionString(fn), old, state);
445    }
446    
447    /**
448     * Update the Momentary state of a single function. 
449     * Updates function value and ChangeListener. 
450     * Does not send outward message TO hardware.
451     *
452     * @param fn    Momentary Function Number 0-28
453     * @param state On - True, Off - False
454     */
455    public void updateFunctionMomentary(int fn, boolean state) {
456        if (fn < 0 || fn > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1) {
457            log.warn("Unhandled update momentary function number: {}", fn);
458            return;
459        }
460        boolean old = FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fn];
461        FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fn] = state;
462        firePropertyChange(Throttle.getFunctionMomentaryString(fn), old, state);
463    }
464
465    /**
466     * Send the message to set the state of functions F0, F1, F2, F3, F4.
467     * <p>
468     * This is used in the setFn implementations provided in this class, but a
469     * real implementation needs to be provided.
470     */
471    protected void sendFunctionGroup1() {
472        log.error("sendFunctionGroup1 needs to be implemented if invoked");
473    }
474
475    /**
476     * Send the message to set the state of functions F5, F6, F7, F8.
477     * <p>
478     * This is used in the setFn implementations provided in this class, but a
479     * real implementation needs to be provided.
480     */
481    protected void sendFunctionGroup2() {
482        log.error("sendFunctionGroup2 needs to be implemented if invoked");
483    }
484
485    /**
486     * Send the message to set the state of functions F9, F10, F11, F12.
487     * <p>
488     * This is used in the setFn implementations provided in this class, but a
489     * real implementation needs to be provided.
490     */
491    protected void sendFunctionGroup3() {
492        log.error("sendFunctionGroup3 needs to be implemented if invoked");
493    }
494
495    /**
496     * Send the message to set the state of functions F13, F14, F15, F16, F17,
497     * F18, F19, F20.
498     * <p>
499     * This is used in the setFn implementations provided in this class, but a
500     * real implementation needs to be provided.
501     */
502    protected void sendFunctionGroup4() {
503        DccLocoAddress a = (DccLocoAddress) getLocoAddress();
504        byte[] result = jmri.NmraPacket.function13Through20Packet(
505                a.getNumber(), a.isLongAddress(),
506                getF13(), getF14(), getF15(), getF16(),
507                getF17(), getF18(), getF19(), getF20());
508
509        //if the result returns as null, we should quit.
510        if (result == null) {
511            return;
512        }
513        CommandStation c;
514        if ((adapterMemo != null) && (adapterMemo.get(jmri.CommandStation.class) != null)) {
515            c = adapterMemo.get(jmri.CommandStation.class);
516        } else {
517            c = InstanceManager.getNullableDefault(CommandStation.class);
518        }
519
520        // send it 3 times
521        if (c != null) {
522            c.sendPacket(result, 3);
523        } else {
524            log.error("Can't send F13-F20 since no command station defined");
525        }
526    }
527
528    /**
529     * Send the message to set the state of functions F21, F22, F23, F24, F25,
530     * F26, F27, F28.
531     * <p>
532     * This is used in the setFn implementations provided in this class, but a
533     * real implementation needs to be provided.
534     */
535    protected void sendFunctionGroup5() {
536        DccLocoAddress a = (DccLocoAddress) getLocoAddress();
537        byte[] result = jmri.NmraPacket.function21Through28Packet(
538                a.getNumber(), a.isLongAddress(),
539                getF21(), getF22(), getF23(), getF24(),
540                getF25(), getF26(), getF27(), getF28());
541        //if the result returns as null, we should quit.
542        if (result == null) {
543            return;
544        }
545        CommandStation c;
546        if ((adapterMemo != null) && (adapterMemo.get(jmri.CommandStation.class) != null)) {
547            c = adapterMemo.get(jmri.CommandStation.class);
548        } else {
549            c = InstanceManager.getNullableDefault(CommandStation.class);
550        }
551
552        // send it 3 times
553        if (c != null) {
554            c.sendPacket(result, 3);
555        } else {
556            log.error("Can't send F21-F28 since no command station defined");
557        }
558    }
559
560    /**
561     * Sets Momentary Function and sends to layout.
562     * {@inheritDoc}
563     */
564    @Override
565    public void setFunctionMomentary(int momFuncNum, boolean state){
566        if (momFuncNum < 0 || momFuncNum > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1) {
567            log.warn("Unhandled set momentary function number: {}", momFuncNum);
568            return;
569        }
570        boolean old = FUNCTION_MOMENTARY_BOOLEAN_ARRAY[momFuncNum];
571        FUNCTION_MOMENTARY_BOOLEAN_ARRAY[momFuncNum] = state;
572        sendFunctionGroup(momFuncNum,true);
573        firePropertyChange(Throttle.getFunctionMomentaryString(momFuncNum), old, state);
574    }
575
576    /**
577     * Send the message to set the momentary state of functions F0, F1, F2, F3,
578     * F4.
579     * <p>
580     * This is used in the setFnMomentary implementations provided in this
581     * class, a real implementation needs to be provided if the hardware
582     * supports setting functions momentary.
583     */
584    protected void sendMomentaryFunctionGroup1() {
585    }
586
587    /**
588     * Send the message to set the momentary state of functions F5, F6, F7, F8.
589     * <p>
590     * This is used in the setFnMomentary implementations provided in this
591     * class, but a real implementation needs to be provided if the hardware
592     * supports setting functions momentary.
593     */
594    protected void sendMomentaryFunctionGroup2() {
595    }
596
597    /**
598     * Send the message to set the Momentary state of functions F9, F10, F11,
599     * F12
600     * <p>
601     * This is used in the setFnMomentary implementations provided in this
602     * class, but a real implementation needs to be provided if the hardware
603     * supports setting functions momentary.
604     */
605    protected void sendMomentaryFunctionGroup3() {
606    }
607
608    /**
609     * Send the message to set the Momentary state of functions F13, F14, F15,
610     * F16, F17, F18, F19, F20
611     * <p>
612     * This is used in the setFnMomentary implementations provided in this
613     * class, but a real implementation needs to be provided if the hardware
614     * supports setting functions momentary.
615     */
616    protected void sendMomentaryFunctionGroup4() {
617    }
618
619    /**
620     * Send the message to set the Momentary state of functions F21, F22, F23,
621     * F24, F25, F26, F27, F28
622     * <p>
623     * This is used in the setFnMomentary implementations provided in this
624     * class, but a real implementation needs to be provided if the hardware
625     * supports setting functions momentary.
626     */
627    protected void sendMomentaryFunctionGroup5() {
628    }
629
630    /**
631     * Set the speed step value. Default should be 128 speed step mode in most
632     * cases.
633     * <p>
634     * Specific implementations should override this function.
635     *
636     * @param mode the current speed step mode
637     */
638    @Override
639    public void setSpeedStepMode(SpeedStepMode mode) {
640        log.debug("Speed Step Mode Change from:{} to:{}", speedStepMode, mode);
641        firePropertyChange(SPEEDSTEPS, speedStepMode, speedStepMode = mode);
642    }
643
644    @Override
645    public SpeedStepMode getSpeedStepMode() {
646        return speedStepMode;
647    }
648
649    long durationRunning = 0;
650    protected long start;
651
652    /**
653     * Processes updated speed from subclasses. Tracks total operating time for
654     * the roster entry by starting the clock if speed is non-zero or stopping
655     * the clock otherwise.
656     *
657     * @param speed the current speed
658     */
659    protected void record(float speed) {
660        if (re == null) {
661            return;
662        }
663        if (speed == 0) {
664            stopClock();
665        } else {
666            startClock();
667        }
668    }
669
670    protected void startClock() {
671        if (start == 0) {
672            start = System.currentTimeMillis();
673        }
674    }
675
676    void stopClock() {
677        if (start == 0) {
678            return;
679        }
680        long stop = System.currentTimeMillis();
681        //Set running duration in seconds
682        durationRunning = durationRunning + ((stop - start) / 1000);
683        start = 0;
684    }
685
686    protected void finishRecord() {
687        if (re == null) {
688            return;
689        }
690        stopClock();
691        String currentDurationString = re.getAttribute("OperatingDuration");
692        long currentDuration = 0;
693        if (currentDurationString == null) {
694            currentDurationString = "0";
695            log.info("operating duration for {} starts as zero", getLocoAddress());
696        }
697        try {
698            currentDuration = Long.parseLong(currentDurationString);
699        } catch (NumberFormatException e) {
700            log.warn("current stored duration is not a valid number \"{} \"", currentDurationString);
701        }
702        currentDuration = currentDuration + durationRunning;
703        re.putAttribute("OperatingDuration", "" + currentDuration);
704        re.putAttribute("LastOperated", new StdDateFormat().format(new Date()));
705        //Only store if the roster entry isn't open.
706        if (!re.isOpen()) {
707            re.store();
708        } else {
709            log.warn("Roster Entry {} running time not saved as entry is already open for editing", re.getId());
710        }
711        re = null;
712    }
713
714    BasicRosterEntry re = null;
715
716    @Override
717    public void setRosterEntry(BasicRosterEntry re) {
718        this.re = re;
719    }
720
721    @Override
722    public BasicRosterEntry getRosterEntry() {
723        return re;
724    }
725
726    /**
727     * Get an integer speed for the given raw speed value. This is a convenience
728     * method that calls {@link #intSpeed(float, int)} with a maxStep of 127.
729     *
730     * @param speed the speed as a percentage of maximum possible speed;
731     *              negative values indicate a need for an emergency stop
732     * @return an integer in the range 0-127
733     */
734    protected int intSpeed(float speed) {
735        return this.intSpeed(speed, 127);
736    }
737
738    /**
739     * Get an integer speed for the given raw speed value.
740     *
741     * @param speed the speed as a percentage of maximum possible speed;
742     *              negative values indicate a need for an emergency stop
743     * @param steps number of possible speeds; values less than 2 will cause
744     *              errors
745     * @return an integer in the range 0-steps
746     */
747    protected int intSpeed(float speed, int steps) {
748        // test that speed is < 0 for emergency stop since calculation of
749        // value returns 0 for some values of -1 < rawSpeed < 0
750        if (speed < 0) {
751            return 1; // emergency stop
752        }
753        // since Emergency Stop (estop) is speed 1, and a negative speed
754        // is used for estop, subtract 1 from steps to avoid the estop
755        // Use ceil() to prevent smaller positive values from being 0
756        int value = (int) Math.ceil((steps - 1) * speed);
757        if (value < 0) {
758            // if we get here, something is wrong and needs to be reported.
759            Exception ex = new Exception("Error calculating speed. Please send logs to the JMRI developers.");
760            log.error(ex.getMessage(), ex);
761            return 1;
762        } else if (value >= steps) {
763            return steps; // maximum possible speed
764        } else if (value > 0) {
765            return value + 1; // add 1 to the value to avoid the estop
766        } else {
767            return 0; // non-emergency stop
768        }
769    }
770
771    // initialize logging
772    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractThrottle.class);
773
774}