001package jmri.jmrit.simpleclock;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.beans.PropertyChangeListener;
006import java.time.Instant;
007import java.util.Calendar;
008import java.util.Date;
009
010import jmri.*;
011import jmri.jmrix.internal.InternalSystemConnectionMemo;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Provide basic Timebase implementation from system clock.
018 * <p>
019 * This implementation provides for the internal clock and for one hardware
020 * clock. A number of hooks and comments are provided below for implementing
021 * multiple hardware clocks should that ever be done.
022 * <p>
023 * The setTimeValue member is the fast time when the clock started. The
024 * startAtTime member is the wall-clock time when the clock was started.
025 * Together, those can be used to calculate the current fast time.
026 * <p>
027 * The pauseTime member is used to indicate that the Timebase was paused. If
028 * non-null, it indicates the current fast time when the clock was paused.
029 *
030 * @author Bob Jacobsen Copyright (C) 2004, 2007 Dave Duchamp - 2007
031 *         additions/revisions for handling one hardware clock
032 */
033public class SimpleTimebase extends jmri.implementation.AbstractNamedBean implements Timebase {
034
035    public static final double MINIMUM_RATE = 0.1;
036    public static final double MAXIMUM_RATE = 100;
037
038    protected final SystemConnectionMemo memo;
039
040    public SimpleTimebase(InternalSystemConnectionMemo memo) {
041        super("SIMPLECLOCK");
042        this.memo = memo;
043        // initialize time-containing memory
044        try {
045            clockMemory = InstanceManager.memoryManagerInstance().provideMemory(memo.getSystemPrefix()+"MCURRENTTIME");
046            clockMemory.setValue("--");
047        } catch (IllegalArgumentException ex) {
048            log.warn("Unable to create CURRENTTIME time memory variable");
049        }
050
051        init();
052
053    }
054
055    final void init(){
056
057        // set to start counting from now
058        setTime(new Date());
059        pauseTime = null;
060        // initialize start/stop sensor for time running
061        try {
062            clockSensor = InstanceManager.sensorManagerInstance().provideSensor(memo.getSystemPrefix()+"SCLOCKRUNNING");
063            clockSensor.setKnownState(Sensor.ACTIVE);
064            clockSensor.addPropertyChangeListener(this::clockSensorChanged);
065        } catch (JmriException e) {
066            log.warn("Exception setting CLOCKRUNNING sensor ACTIVE", e);
067        }
068        // initialize rate factor-containing memory
069        if (InstanceManager.getNullableDefault(MemoryManager.class) != null) {
070            // only try to create memory if memories are supported
071            try {
072                factorMemory = InstanceManager.memoryManagerInstance().provideMemory(memo.getSystemPrefix()+"MRATEFACTOR");
073                factorMemory.setValue(userGetRate());
074            } catch (IllegalArgumentException ex) {
075                log.warn("Unable to create RATEFACTOR time memory variable");
076            }
077        }
078
079    }
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    public String getBeanType() {
086        return Bundle.getMessage("BeanNameTime");
087    }
088
089    /**
090     * {@inheritDoc}
091     */
092    @Override
093    public Date getTime() {
094        // is clock stopped?
095        if (pauseTime != null) {
096            return new Date(pauseTime.getTime()); // to ensure not modified outside
097        } // clock running
098        long elapsedMSec = (new Date()).getTime() - startAtTime.getTime();
099        long nowMSec = setTimeValue.getTime() + (long) (mFactor * elapsedMSec);
100        return new Date(nowMSec);
101    }
102
103    /**
104     * {@inheritDoc}
105     */
106    @Override
107    public void setTime(Date d) {
108        startAtTime = new Date(); // set now in wall clock time
109        setTimeValue = new Date(d.getTime()); // to ensure not modified from outside
110        if (synchronizeWithHardware) {
111            // send new time to all hardware clocks, except the hardware time source if there is one
112            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
113            if (InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) {
114                InstanceManager.getDefault(ClockControl.class).setTime(d);
115            }
116        }
117        if (pauseTime != null) {
118            pauseTime = setTimeValue; // if stopped, continue stopped at new time
119        }
120        handleAlarm(null);
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public void setTime(Instant i) {
128        setTime(Date.from(i));
129    }
130
131    /**
132     * {@inheritDoc}
133     */
134    @Override
135    public void userSetTime(Date d) {
136        // this call only results from user changing fast clock time in Setup Fast Clock
137        startAtTime = new Date(); // set now in wall clock time
138        setTimeValue = new Date(d.getTime()); // to ensure not modified from outside
139        if (synchronizeWithHardware) {
140            // send new time to all hardware clocks, including the hardware time source if there is one
141            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
142            InstanceManager.getDefault(jmri.ClockControl.class).setTime(d);
143        } else if (!internalMaster && (hardwareTimeSource != null)) {
144            // if not synchronizing, send to the hardware time source if there is one
145            hardwareTimeSource.setTime(d);
146        }
147        if (pauseTime != null) {
148            pauseTime = setTimeValue; // if stopped, continue stopped at new time
149        }
150        handleAlarm(null);
151    }
152
153    /**
154     * {@inheritDoc}
155     */
156    @Override
157    public void setRun(boolean run) {
158        if (run && pauseTime != null) {
159            // starting of stopped clock
160            setTime(pauseTime);
161            if (synchronizeWithHardware) {
162                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
163                InstanceManager.getDefault(ClockControl.class).startHardwareClock(getTime());
164            } else if (!internalMaster && hardwareTimeSource != null) {
165                hardwareTimeSource.startHardwareClock(getTime());
166            }
167            pauseTime = null;
168            if (clockSensor != null) {
169                try {
170                    clockSensor.setKnownState(Sensor.ACTIVE);
171                } catch (JmriException e) {
172                    log.warn("Exception setting ISClockRunning sensor ACTIVE", e);
173                }
174            }
175        } else if (!run && pauseTime == null) {
176            // stopping of running clock:
177            // Store time it was stopped, and stop it
178            pauseTime = getTime();
179            if (synchronizeWithHardware) {
180                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
181                InstanceManager.getDefault(ClockControl.class).stopHardwareClock();
182            } else if (!internalMaster && hardwareTimeSource != null) {
183                hardwareTimeSource.stopHardwareClock();
184            }
185            if (clockSensor != null) {
186                try {
187                    clockSensor.setKnownState(Sensor.INACTIVE);
188                } catch (jmri.JmriException e) {
189                    log.warn("Exception setting ISClockRunning sensor INACTIVE", e);
190                }
191            }
192        }
193        firePropertyChange("run", !run, run); // old, then new
194        handleAlarm(null);
195    }
196
197    /**
198     * {@inheritDoc}
199     */
200    @Override
201    public boolean getRun() {
202        return pauseTime == null;
203    }
204
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public void setRate(double factor) throws TimebaseRateException {
210        checkRateValid(factor);
211        if (internalMaster && (!notInitialized)) {
212            log.error("Probable Error - questionable attempt to change fast clock rate");
213        }
214        double oldFactor = mFactor;
215        Date now = getTime();
216        // actually make the change
217        mFactor = factor;
218        if (internalMaster || notInitialized) {
219            hardwareFactor = factor;
220        }
221        if (internalMaster || (synchronizeWithHardware && notInitialized)) {
222            // send new rate to all hardware clocks, except the hardware time source if there is one
223            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
224            if (InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) {
225                InstanceManager.getDefault(ClockControl.class).setRate(factor);
226            }
227        }
228        // make sure time is right with new rate
229        setTime(now);
230        // notify listeners if internal master
231        if (internalMaster) {
232            firePropertyChange("rate", oldFactor, factor); // old, then new
233        }
234        handleAlarm(null);
235    }
236
237    /**
238     * {@inheritDoc}
239     */
240    @Override
241    public void userSetRate(double factor) throws TimebaseRateException {
242        // this call is used when user changes fast clock rate either in Setup Fast Clock or via a ClockControl
243        // implementation
244        checkRateValid(factor);
245        double oldFactor = hardwareFactor;
246        Date now = getTime();
247        // actually make the change
248        mFactor = factor;
249        hardwareFactor = factor;
250        if (synchronizeWithHardware) {
251            // send new rate to all hardware clocks, including the hardware time source if there is one
252            // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
253            InstanceManager.getDefault(ClockControl.class).setRate(factor);
254        } else if (!internalMaster && (hardwareTimeSource != null)) {
255            // if not synchronizing, send to the hardware time source if there is one
256            hardwareTimeSource.setRate(factor);
257        }
258        // make sure time is right with new rate
259        setTime(now);
260        // update memory
261        updateMemory(factor);
262        // notify listeners
263        firePropertyChange("rate", oldFactor, factor); // old, then new
264        handleAlarm(null);
265    }
266
267    private void checkRateValid(double factor) throws TimebaseRateException {
268        if (factor < MINIMUM_RATE || factor > MAXIMUM_RATE) {
269            log.error("rate of {} is out of reasonable range {} - {}", factor, MINIMUM_RATE, MAXIMUM_RATE);
270            throw new TimebaseRateException(Bundle.getMessage("IncorrectRate", factor, MINIMUM_RATE, MAXIMUM_RATE));
271        }
272    }
273
274    /**
275     * {@inheritDoc}
276     */
277    @Override
278    public double getRate() {
279        return mFactor;
280    }
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    public double userGetRate() {
287        return ( internalMaster ? mFactor : hardwareFactor);
288    }
289
290    /**
291     * {@inheritDoc}
292     */
293    @Override
294    public void setInternalMaster(boolean master, boolean update) {
295        if (master != internalMaster) {
296            internalMaster = master;
297            if (internalMaster) {
298                mFactor = hardwareFactor; // get rid of any fiddled rate present
299            }
300            if (update) {
301                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
302                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(userGetRate(),
303                        getTime(), false);
304            }
305
306            if (internalMaster) {
307                masterName = "";
308                hardwareTimeSource = null;
309            } else {
310                // Note if there are multiple hardware clocks, this should be changed to correctly
311                // identify which hardware clock has been chosen-currently assumes only one
312                hardwareTimeSource = InstanceManager.getDefault(ClockControl.class);
313                masterName = hardwareTimeSource.getHardwareClockName();
314            }
315        }
316    }
317
318    /**
319     * {@inheritDoc}
320     */
321    @Override
322    public boolean getInternalMaster() {
323        return internalMaster;
324    }
325
326    /**
327     * {@inheritDoc}
328     */
329    @Override
330    public void setMasterName(String name) {
331        if (!internalMaster) {
332            masterName = name;
333            // if multiple clocks, this must be replaced by a loop over all hardware clocks to identify
334            // the one that is the hardware time source
335            hardwareTimeSource = InstanceManager.getDefault(ClockControl.class);
336        } else {
337            masterName = "";
338            hardwareTimeSource = null;
339        }
340    }
341
342    /**
343     * {@inheritDoc}
344     */
345    @Override
346    public String getMasterName() {
347        return masterName;
348    }
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    public void setSynchronize(boolean synchronize, boolean update) {
355        if (synchronizeWithHardware != synchronize) {
356            synchronizeWithHardware = synchronize;
357            if (update) {
358                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
359                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
360                   userGetRate(), getTime(), false);
361            }
362        }
363    }
364
365    /**
366     * {@inheritDoc}
367     */
368    @Override
369    public boolean getSynchronize() {
370        return synchronizeWithHardware;
371    }
372
373    /**
374     * If update true, calls initializeHardwareClock.
375     * {@inheritDoc}
376     */
377    @Override
378    public void setCorrectHardware(boolean correct, boolean update) {
379        if (correctHardware != correct) {
380            correctHardware = correct;
381            if (update) {
382                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
383                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
384                userGetRate(), getTime(), false);
385            }
386        }
387    }
388
389    /**
390     * {@inheritDoc}
391     */
392    @Override
393    public boolean getCorrectHardware() {
394        return correctHardware;
395    }
396
397    /**
398     * {@inheritDoc}
399     */
400    @Override
401    public void set12HourDisplay(boolean display, boolean update) {
402        if (display != display12HourClock) {
403            display12HourClock = display;
404            if (update) {
405                // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks
406                InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(
407                userGetRate(), getTime(), false);
408            }
409        }
410    }
411
412    /**
413     * {@inheritDoc}
414     */
415    @Override
416    public boolean use12HourDisplay() {
417        return display12HourClock;
418    }
419
420    /**
421     * {@inheritDoc}
422     */
423    @Override
424    public void setClockInitialRunState(ClockInitialRunState state) {
425        initialState = state;
426    }
427
428    /**
429     * {@inheritDoc}
430     */
431    @Override
432    public ClockInitialRunState getClockInitialRunState() {
433        return initialState;
434    }
435
436    /**
437     * {@inheritDoc}
438     */
439    @Override
440    public void setShowStopButton(boolean displayed) {
441        showStopButton = displayed;
442    }
443
444    /**
445     * {@inheritDoc}
446     */
447    @Override
448    public boolean getShowStopButton() {
449        return showStopButton;
450    }
451
452    /**
453     * {@inheritDoc}
454     */
455    @Override
456    public void setStartSetTime(boolean set, Date time) {
457        startSetTime = set;
458        startTime = new Date(time.getTime());
459    }
460
461    /**
462     * {@inheritDoc}
463     */
464    @Override
465    public boolean getStartSetTime() {
466        return startSetTime;
467    }
468
469    /**
470     * {@inheritDoc}
471     */
472    @Override
473    public void setStartRate(double factor) {
474        startupFactor = factor;
475        haveStartupFactor = true;
476    }
477
478    /**
479     * {@inheritDoc}
480     */
481    @Override
482    public double getStartRate() {
483        if (haveStartupFactor) {
484            return startupFactor;
485        } else {
486            return userGetRate();
487        }
488    }
489
490    /**
491     * {@inheritDoc}
492     */
493    @Override
494    public void setSetRateAtStart(boolean set) {
495        startSetRate = set;
496    }
497
498    /**
499     * {@inheritDoc}
500     */
501    @Override
502    public boolean getSetRateAtStart() {
503        return startSetRate;
504    }
505
506    /**
507     * {@inheritDoc}
508     */
509    @Override
510    public Date getStartTime() {
511        return new Date(startTime.getTime());
512    }
513
514    /**
515     * {@inheritDoc}
516     */
517    @Override
518    public void setStartClockOption(int option) {
519        startClockOption = option;
520    }
521
522    /**
523     * {@inheritDoc}
524     */
525    @Override
526    public int getStartClockOption() {
527        return startClockOption;
528    }
529
530    /**
531     * The following method should only be invoked at start up.
532     * {@inheritDoc}
533     */
534    @Override
535    public void initializeClock() {
536        switch (startClockOption) {
537            case NIXIE_CLOCK:
538                jmri.jmrit.nixieclock.NixieClockFrame f = new jmri.jmrit.nixieclock.NixieClockFrame();
539                f.setVisible(true);
540                break;
541            case ANALOG_CLOCK:
542                jmri.jmrit.analogclock.AnalogClockFrame g = new jmri.jmrit.analogclock.AnalogClockFrame();
543                g.setVisible(true);
544                break;
545            case LCD_CLOCK:
546                jmri.jmrit.lcdclock.LcdClockFrame h = new jmri.jmrit.lcdclock.LcdClockFrame();
547                h.setVisible(true);
548                break;
549            case PRAGOTRON_CLOCK:
550                jmri.jmrit.pragotronclock.PragotronClockFrame p = new jmri.jmrit.pragotronclock.PragotronClockFrame();
551                p.setVisible(true);
552                break;
553            default:
554                log.debug("initializeClock() called with invalid startClockOption: {}", startClockOption);
555        }
556    }
557
558    /**
559     * {@inheritDoc}
560     */
561    @Override
562    public void initializeHardwareClock() {
563        boolean startStopped = (initialState == ClockInitialRunState.DO_STOP);
564        if (synchronizeWithHardware || correctHardware) {
565            if (startStopped) {
566                InstanceManager.getList(ClockControl.class).forEach( cc -> 
567                    cc.initializeHardwareClock( 0, getTime(), (!internalMaster && !startSetTime)) );
568            } else {
569                InstanceManager.getList(ClockControl.class).forEach( cc -> 
570                    cc.initializeHardwareClock( mFactor, getTime(), (!internalMaster && !startSetTime)) );
571            }
572        } else if (!internalMaster) {
573            if (startStopped) {
574                hardwareTimeSource.initializeHardwareClock(0, getTime(), (!startSetTime));
575            } else {
576                hardwareTimeSource.initializeHardwareClock(hardwareFactor, getTime(), (!startSetTime));
577            }
578        }
579        notInitialized = false;
580    }
581
582    /**
583     * {@inheritDoc}
584     */
585    @Override
586    public boolean getIsInitialized() {
587        return (!notInitialized);
588    }
589
590    /**
591     * Handle a change in the clock running sensor
592     */
593    private void clockSensorChanged(java.beans.PropertyChangeEvent e) {
594        if (clockSensor.getKnownState() == Sensor.ACTIVE) {
595            // simply return if clock is already running
596            if (pauseTime == null) {
597                return;
598            }
599            setRun(true);
600        } else {
601            // simply return if clock is already stopped
602            if (pauseTime != null) {
603                return;
604            }
605            setRun(false);
606        }
607    }
608
609    /**
610     * Stops Timer.
611     * {@inheritDoc}
612     */
613    @Override
614    public void dispose() {
615        if (timer != null) {
616            // end this timer
617            timer.setRepeats(false); // just in case
618            timer.stop();
619
620            ActionListener listeners[] = timer.getListeners(ActionListener.class);
621            for (ActionListener listener : listeners) {
622                timer.removeActionListener(listener);
623            }
624            timer = null;
625        }
626        if ( clockSensor != null ) {
627            clockSensor.removePropertyChangeListener(this::clockSensorChanged);
628        }
629        super.dispose(); // remove standard property change listeners
630    }
631
632    /**
633     * InstanceManager.getDefault(jmri.Timebase.class) variables and options
634     */
635    private double mFactor = 1.0; // this is the rate factor for the JMRI fast clock
636    private double hardwareFactor = 1.0; // this is the rate factor for the hardware clock
637    //  The above is necessary to support hardware clock Time Sources that fiddle with mFactor to
638    //      synchronize, instead of sending over a new time to synchronize.
639    private double startupFactor = 1.0; // this is the rate requested at startup
640    private boolean startSetRate = true; // if true, the hardware rate will be set to
641    private boolean haveStartupFactor = false; // true if startup factor was ever set.
642    // startupFactor at startup.
643
644    private Date startAtTime;
645    private Date setTimeValue;
646    private Date pauseTime; // null value indicates clock is running
647    private Sensor clockSensor = null; // active when clock is running, inactive when stopped
648    private Memory clockMemory = null; // contains current time on each tick
649    private Memory factorMemory = null; // contains the rate factor for the fast clock
650
651    private boolean internalMaster = true; // false indicates a hardware clock is the master
652    private String masterName = ""; // name of hardware time source, if not internal master
653    private ClockControl hardwareTimeSource = null; // ClockControl instance of hardware time source
654    private boolean synchronizeWithHardware = false; // true indicates need to synchronize
655    private boolean correctHardware = false; // true indicates hardware correction requested
656    private boolean display12HourClock = false; // true if 12-hour clock display is requested
657    private ClockInitialRunState initialState = ClockInitialRunState.DO_START; // what to do with the clock running state at startup
658    private boolean startSetTime = false; // true indicates set fast clock to specified time at
659    //start up requested
660    private Date startTime = new Date(); // specified time for setting fast clock at start up
661    private int startClockOption = NONE; // request start of a clock at start up
662    private boolean notInitialized = true; // true before initialization received from start up
663    private boolean showStopButton = false; // true indicates start up with start/stop button displayed
664
665    private java.text.SimpleDateFormat timeStorageFormat = null;
666
667    private javax.swing.Timer timer = null;
668
669    /**
670     * Start the minute alarm ticking, if it isnt already.
671     */
672    void startAlarm() {
673        if (timer == null) {
674            handleAlarm(null);
675        }
676    }
677
678    private int oldHours = -1;
679    private int oldMinutes = -1;
680    private Date oldDate = null;
681
682    /**
683     * Handle an "alarm", which is used to count off minutes.
684     * <p>
685     * Listeners will be notified if the hours or minutes changed
686     * since the last time.
687     * @param e Event which triggered this
688     */
689    void handleAlarm(ActionEvent e) {
690        // on first pass, set up the timer to call this routine
691        if (timer == null) {
692            timer = new javax.swing.Timer(60 * 1000, this::handleAlarm);
693        }
694
695        Calendar calendar = Calendar.getInstance();
696        timer.stop();
697        Date date = getTime();
698        calendar.setTime(date);
699        int waitSeconds = 60 - calendar.get(Calendar.SECOND);
700        int delay = (int) (waitSeconds * 1000 / mFactor) + 100; // make sure you miss the time transition
701        timer.setInitialDelay(delay);
702        timer.setRepeats(true); // in case we run by
703        timer.start();
704
705        // and notify the others
706        calendar.setTime(date);
707        int hours = calendar.get(Calendar.HOUR_OF_DAY);
708        int minutes = calendar.get(Calendar.MINUTE);
709        if (hours != oldHours || minutes != oldMinutes) {
710            // update memory
711            updateMemory(date);
712            // notify listeners
713            firePropertyChange("minutes", Double.valueOf(oldMinutes), Double.valueOf(minutes));
714            firePropertyChange("time", oldDate != null ? new Date(oldDate.getTime()) : null, new Date(date.getTime())); // to ensure not modified outside
715        }
716        oldDate = date;
717        oldHours = hours;
718        oldMinutes = minutes;
719    }
720
721    void updateMemory(Date date) {
722        if (timeStorageFormat == null) {
723            String pattern = java.util.ResourceBundle.getBundle("jmri.jmrit.simpleclock.SimpleClockBundle")
724                .getString("TimeStorageFormat");
725            try {
726                timeStorageFormat = new java.text.SimpleDateFormat(pattern);
727            } catch (IllegalArgumentException e) {
728                log.info("Unable to parse date / time format: {}",pattern);
729                log.info("For supported formats see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/SimpleDateFormat.html");
730                log.info("Dropping back to default time format (h:mm a) 4:56 PM, due to exception", e);
731                timeStorageFormat = new java.text.SimpleDateFormat("h:mm a");
732            }
733        }
734        clockMemory.setValue(timeStorageFormat.format(date));
735    }
736
737    void updateMemory(double factor) {
738        factorMemory.setValue(factor);
739    }
740
741    /**
742     * {@inheritDoc}
743     */
744    @Override
745    public void addMinuteChangeListener(PropertyChangeListener l) {
746        addPropertyChangeListener("minutes", l);
747    }
748
749    /**
750     * {@inheritDoc}
751     */
752    @Override
753    public void removeMinuteChangeListener(PropertyChangeListener l) {
754        removePropertyChangeListener("minutes", l);
755    }
756
757    /**
758     * {@inheritDoc}
759     */
760    @Override
761    public PropertyChangeListener[] getMinuteChangeListeners() {
762        return getPropertyChangeListeners("minutes");
763    }
764
765    @Override
766    public void addPropertyChangeListener(PropertyChangeListener listener) {
767        super.addPropertyChangeListener(listener);
768        startAlarm();
769    }
770
771
772    @Override
773    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
774        super.addPropertyChangeListener(propertyName, listener);
775        if (propertyName != null && (propertyName.equals("minutes") || propertyName.equals("time"))) {
776            startAlarm();
777        }
778    }
779
780    /**
781     * Implementation does nothing.
782     * {@inheritDoc}
783     */
784    @Override
785    public void setState(int s) throws jmri.JmriException {
786    }
787
788    /**
789     * Implementation returns 0 .
790     * {@inheritDoc}
791     */
792    @Override
793    public int getState() {
794        return 0;
795    }
796
797    private final static Logger log = LoggerFactory.getLogger(SimpleTimebase.class);
798
799}