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