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