001package jmri.jmrix.openlcb; 002 003import org.openlcb.NodeID; 004import org.openlcb.OlcbInterface; 005import org.openlcb.protocols.TimeBroadcastConsumer; 006import org.openlcb.protocols.TimeBroadcastGenerator; 007import org.openlcb.protocols.TimeProtocol; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import java.beans.PropertyChangeListener; 012import java.util.Date; 013 014import jmri.Timebase; 015import jmri.TimebaseRateException; 016import jmri.implementation.DefaultClockControl; 017import jmri.util.ThreadingUtil; 018 019/** 020 * Implementation of the ClockControl interface for JMRI using the OpenLCB clock listener or generator. 021 * 022 * @author Balazs Racz, 2018 023 */ 024 025public class OlcbClockControl extends DefaultClockControl { 026 public OlcbClockControl(OlcbInterface iface, NodeID clockID, boolean isMaster) { 027 this.clockId = clockID; 028 if (isMaster) { 029 generator = new TimeBroadcastGenerator(iface, clockID); 030 hardwareClock = generator; 031 } else { 032 consumer = new TimeBroadcastConsumer(iface, clockID); 033 hardwareClock = consumer; 034 } 035 jmriClock = jmri.InstanceManager.getDefault(jmri.Timebase.class); 036 listener = propertyChangeEvent -> clockUpdate(propertyChangeEvent.getPropertyName(), propertyChangeEvent.getNewValue()); 037 hardwareClock.addPropertyChangeListener(listener); 038 } 039 040 public void dispose() { 041 hardwareClock.removePropertyChangeListener(listener); 042 if (consumer != null) { 043 consumer.dispose(); 044 consumer = null; 045 hardwareClock = null; 046 } 047 } 048 049 /// Called when the layout sends an update to state, for example when someone else operates a 050 /// clock controlling node. 051 private void clockUpdate(String property, Object newValue) { 052 switch (property) { 053 case TimeProtocol.PROP_RUN_UPDATE: 054 jmriClock.setRun(hardwareClock.isRunning()); 055 break; 056 case TimeProtocol.PROP_RATE_UPDATE: 057 try { 058 jmriClock.userSetRate(hardwareClock.getRate()); 059 } catch (TimebaseRateException e) { 060 log.warn("Failed to set OpenLCB rate to internal clock."); 061 } 062 break; 063 case TimeProtocol.PROP_TIME_UPDATE: 064 jmriClock.setTime(new Date(hardwareClock.getTimeInMsec())); 065 break; 066 default: 067 // no default action. 068 } 069 } 070 071 @Override 072 public String getHardwareClockName() { 073 String clockName; 074 if (clockId.equals(TimeProtocol.DEFAULT_CLOCK)) { 075 clockName = Bundle.getMessage("OlcbClockDefault"); 076 } else if (clockId.equals(TimeProtocol.DEFAULT_RT_CLOCK)) { 077 clockName = Bundle.getMessage("OlcbClockDefaultRT"); 078 } else if (clockId.equals(TimeProtocol.ALT_CLOCK_1)) { 079 clockName = Bundle.getMessage("OlcbClockAlt1"); 080 } else if (clockId.equals(TimeProtocol.ALT_CLOCK_2)) { 081 clockName = Bundle.getMessage("OlcbClockAlt2"); 082 } else { 083 clockName = Bundle.getMessage("OlcbClockCustom", clockId.toString()); 084 } 085 if (consumer != null) { 086 return Bundle.getMessage("OlcbClockListenerFor", clockName); 087 } else { 088 return Bundle.getMessage("OlcbClockGeneratorFor", clockName); 089 } 090 } 091 092 @Override 093 public boolean canCorrectHardwareClock() { 094 return false; 095 } 096 097 @Override 098 public boolean canSet12Or24HourClock() { 099 return false; 100 } 101 102 @Override 103 public boolean requiresIntegerRate() { 104 return false; 105 } 106 107 @Override 108 public double getRate() { 109 return hardwareClock.getRate(); 110 } 111 112 @Override 113 public Date getTime() { 114 return new Date(hardwareClock.getTimeInMsec()); 115 } 116 117 @Override 118 public void stopHardwareClock() { 119 hardwareClock.requestStop(); 120 } 121 122 @Override 123 public void startHardwareClock(Date now) { 124 hardwareClock.requestSetTime(now.getTime()); 125 hardwareClock.requestStart(); 126 } 127 128 @Override 129 public void setRate(double newRate) { 130 // OpenLCB rates are 0.25 resolution, so we use half of that as minimum threshold. 131 if (Math.abs(hardwareClock.getRate() - newRate) > 0.12) { 132 hardwareClock.requestSetRate(newRate); 133 } else if (Math.abs(hardwareClock.getRate() - newRate) > 0.0001) { 134 // Trigger update notification that we rejected the change, but not inline. 135 ThreadingUtil.runOnLayoutDelayed(() -> clockUpdate(TimeProtocol.PROP_RATE_UPDATE, null), 50); 136 } 137 138 } 139 140 @Override 141 public void setTime(Date now) { 142 hardwareClock.requestSetTime(now.getTime()); 143 } 144 145 @Override 146 public void initializeHardwareClock(double rate, Date now, boolean getTime) { 147 if (!getTime) { 148 hardwareClock.requestSetTime(now.getTime()); 149 if (rate == 0) { 150 hardwareClock.requestStop(); 151 } else { 152 hardwareClock.requestSetRate(rate); 153 hardwareClock.requestStart(); 154 } 155 } else { 156 hardwareClock.requestQuery(); 157 } 158 } 159 160 /// Stores instance to the JMRI clock master. 161 private final Timebase jmriClock; 162 /// This is the interface to the clock generator or consumer. 163 private TimeProtocol hardwareClock; 164 /// The clock identifier on the OpenLCB bus. 165 private final NodeID clockId; 166 /// If we instantiated a clock consumer, this is the object. 167 private TimeBroadcastConsumer consumer; 168 /// If we instantiated a generator, this is the object 169 private TimeBroadcastGenerator generator; 170 /// The listener registered for the hardwareClock. 171 private final PropertyChangeListener listener; 172 173 private final static Logger log = LoggerFactory.getLogger(OlcbClockControl.class); 174}