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