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}