001package jmri.jmrix.dccpp; 002 003import java.util.Calendar; 004import java.util.Date; 005 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import jmri.InstanceManager; 010import jmri.Timebase; 011import jmri.implementation.DefaultClockControl; 012 013/** 014 * Class providing Clock Control to the DCC-EX client. 015 * Does nothing unless "Synchronize Internal Fast Clock and DCC-EX Fast Clock" is enabled. 016 * If "Time Source" is "Internal Computer Clock", send any changes to Fast Clock Rate or Time 017 * to the command station. 018 * If "Time Source" is "DCC-EX Fast Clock", listen for incoming Time messages and set the Fast 019 * Clock from these. Ignores incoming Rate messages. 020 * 021 * @author mstevetodd 2023 022 */ 023public class DCCppClockControl extends DefaultClockControl implements DCCppListener { 024 025 DCCppSystemConnectionMemo _memo = null; 026 DCCppTrafficController _tc = null; 027 Timebase timebase; 028 Calendar _cal; 029 java.beans.PropertyChangeListener minuteChangeListener; 030 boolean isRunning; //track clock's pause state (Note: timebase.isRun() is updated too late) 031 final static long MSECPERHOUR = 3600000; 032 final static long MSECPERMINUTE = 60000; 033 034 public DCCppClockControl(DCCppSystemConnectionMemo memo) { 035 log.trace("DCCppClockControl (DCCppSystemConnectionMemo {})", memo); // NOI18N 036 037 _memo = memo; 038 _tc = _memo.getDCCppTrafficController(); 039 _tc.addDCCppListener(DCCppInterface.CS_INFO, this); 040 _cal = Calendar.getInstance(); 041 042 timebase = InstanceManager.getNullableDefault(jmri.Timebase.class); 043 if (timebase == null) { 044 log.error("No Internal Timebase Instance"); // NOI18N 045 return; 046 } 047 // Create a timebase listener for the Minute change events 048 minuteChangeListener = new java.beans.PropertyChangeListener() { 049 @Override 050 public void propertyChange(java.beans.PropertyChangeEvent e) { 051 log.trace("minuteChangeListener propertyChange for '{}' from '{}' to '{}'",e.getPropertyName(),e.getOldValue(),e.getNewValue()); 052 setTime(timebase.getTime()); 053 } 054 }; 055 timebase.addMinuteChangeListener(minuteChangeListener); 056 057 } 058 059 /** 060 * Get name of hardware clock, shown in UI 061 */ 062 @Override 063 public String getHardwareClockName() { 064 log.trace("getHardwareClockName()"); // NOI18N 065 return ("DCC-EX Fast Clock"); 066 } 067 068 /** 069 * Send the new fastclock rate to CS if internal is master AND synchronize enabled 070 * send rate of zero if clock is not running 071 * Note: fastclock rate and time are in a single message 072 */ 073 @Override 074 public void setRate(double newRate) { 075 log.trace("setRate({})", (int)newRate); // NOI18N 076 if (timebase.getInternalMaster() && timebase.getSynchronize()) { 077 _cal.setTime(timebase.getTime()); 078 int minutes = _cal.get(Calendar.HOUR)*60+_cal.get(Calendar.MINUTE); 079 if (!isRunning) newRate = 0; //send rate of zero if clock is not running 080 _tc.sendDCCppMessage(DCCppMessage.makeClockSetMsg(minutes, (int)newRate), null); 081 } 082 return; 083 } 084 public void setRate() { 085 setRate(timebase.getRate()); 086 } 087 088 @Override 089 public double getRate() { 090 log.trace("getRate()"); // NOI18N 091 //request that CS return the time (and the rate as they're in same message) 092 _tc.sendDCCppMessage(DCCppMessage.makeClockRequestTimeMsg(), null); 093 return timebase.getRate(); 094 } 095 096 /** 097 * Send the new fast clock time to CS if internal is master AND synchronize enabled 098 * Note: fastclock rate and time are in a single message 099 */ 100 @Override 101 public void setTime(Date newTimestamp) { 102 log.trace("setTime({})", newTimestamp); // NOI18N 103 if (timebase.getInternalMaster() && timebase.getSynchronize()) { 104 _cal.setTime(newTimestamp); 105 int minutes = _cal.get(Calendar.HOUR)*60+_cal.get(Calendar.MINUTE); 106 _tc.sendDCCppMessage(DCCppMessage.makeClockSetMsg(minutes, (int)timebase.getRate()), null); 107 } 108 return; 109 } 110 public void setTime() { 111 setTime(timebase.getTime()); 112 } 113 114 @Override 115 public Date getTime() { 116 log.trace("getTime()"); // NOI18N 117 // send get time message 118 _tc.sendDCCppMessage(DCCppMessage.makeClockRequestTimeMsg(), null); // <JC> 119 // return the current time without waiting for response... (?) 120 return timebase.getTime(); 121 } 122 123 /** 124 * Pause, unpause and initialize fast clock 125 */ 126 @Override 127 public void startHardwareClock(Date now) { 128 log.trace("startHardwareClock({})", now); // NOI18N 129 isRunning = true; 130 setRate(); //notify the CS 131 return; 132 } 133 @Override 134 public void stopHardwareClock() { 135 log.trace("stopHardwareClock()"); // NOI18N 136 isRunning = false; 137 setRate(); //notify the CS 138 return; 139 } 140 @Override 141 public void initializeHardwareClock(double rate, Date now, boolean getTime) { 142 log.trace("initializeHardwareClock(rate={}, time={}, getTime={}) sync={}, internal={}", 143 rate, now, getTime, timebase.getSynchronize(), timebase.getInternalMaster()); // NOI18N 144 isRunning = timebase.getRun(); 145 if (timebase.getSynchronize()) { 146 if (timebase.getInternalMaster()) { 147 setRate(); //notify the CS of time and rate 148 } else { 149 getRate(); //request time and rate from CS 150 } 151 } 152 } 153 154 /** 155 * Prevent user entry of a fractional rate, since DCC-EX only supports integer rates 156 */ 157 @Override 158 public boolean requiresIntegerRate() { 159 log.trace("requiresIntegerRate() returns true"); // NOI18N 160 return true; 161 } 162 163 /* handle incoming clock-related messages 164 * update the time ONLY if synchronize is enabled AND DCC-EX is Master 165 * Ignore the incoming rate, since JMRI only supports changing rate for Internal Masters */ 166 167 @SuppressWarnings("deprecation") 168 @Override 169 public void message(DCCppReply msg) { 170 log.trace("message(DCCppReply {})", msg); // NOI18N 171 if (msg.isClockReply() && timebase.getSynchronize() && timebase.getMasterName().equals(getHardwareClockName())) { 172 log.trace("Clock message(DCCppReply {}), time={}, rate={}", msg, msg.getClockMinutesString(), msg.getClockRateString()); // NOI18N 173 174 //set the new time from message 175 Date today = timebase.getTime(); //current timestamp 176 long ms = today.getTime(); //get current timestamp in msecs 177 ms -= today.getHours() * MSECPERHOUR; //subtract out current hours 178 ms -= today.getMinutes() * MSECPERMINUTE; //subtract out current minutes 179 ms += msg.getClockMinutesInt() * MSECPERMINUTE; //add in new minutes from message 180 timebase.setTime(new Date(ms)); //set the fastclock from this msecs value 181 } 182 } 183 184 /* process outgoing messages and retries, not needed for DCC-EX */ 185 @Override 186 public void message(DCCppMessage msg) { 187 log.trace("message(DCCppMessage {})", msg); // NOI18N 188 } 189 /* if timeout, don't resend */ 190 @Override 191 public void notifyTimeout(DCCppMessage msg) { 192 log.trace("notifyTimeout(DCCppMessage {})", msg); // NOI18N 193 } 194 195 private final static Logger log = LoggerFactory.getLogger(DCCppClockControl.class); 196} 197 198